Exchange Email Address Template Objects in PowerShell

Exchange has a very rich set of objects which are used and created by the various Exchange cmdlets. Unfortunately, these objects (excepting those used by Exchange Web Services) are poorly documented – and the documentation which is available is often incorrect or misleading.

I ran into that problem this past week.

To understand my particular issue, let’s review a little history. Exchange 2000 and Exchange 2003 didn’t have the concept of individual “accepted domains”. Instead, you usually used Recipient Policies to identify the accepted domains. Recipient Policies were also used to create and manage Mailbox Manager policies.

In Exchange 2007, all of the capabilities of Recipient Policies were split into multiple features: Accepted Domains, E-mail Address Policies, and Managed Folder Mailbox policies. In Exchange 2010, Managed Folder Mailbox policies have been deprecated and replaced by Retention Policies.

However, some capabilities of these features are not available in the Exchange 2007/2010 GUI (but were in the Exchange 2000/2003 GUI). One of these hidden features is to have both enabled and disabled address templates in an E-Mail Address Policy. In the case of a disabled address template, a particular e-mail address domain can be manually assigned to a given Exchange object but that e-mail address domain will not be automatically assigned even if the object otherwise meets the requirements of the recipient filter assigned to the E-Mail Address Policy (such as a company name, a conditional attribute, group membership, etc.).

Whew.

So what happens when you need to disable an existing e-mail address template?

Well, you do it in PowerShell. (Long lead up to an obvious response, right?) And you do it with the obvious PowerShell cmdlet: Set-EmailAddressPolicy.

What would seem to make sense that the command would be something like:

Set-EmailAddressPolicy ‘Default Policy’ -Add -DisabledEmailAddressTemplates ‘@example.com’

…but that doesn’t work. Aside from the fact that there is no Add or Remove parameter to Set-EmailAddressPolicy, you will eventually figure out that whatever value is provided to the EnabledEmailAddressTemplates and the DisabledEmailAddressTemplates parameters overwrite the values previously there. Well, ain’t that a kick?

To change an address template collection, you must modify the existing collection and resubmit the modified value to Set-EmailAddressPolicy.

So what’s the problem? The problem is the type of the required argument. The EnabledEmailAddressTemplates and DisabledEmailAddressTemplates parameters take a parameter of type Microsoft.Exchange.Data.ProxyAddressTemplateCollection. This is an enumerated collection (a fancy name for an array that you can use with a foreach(), or index it, or use the Item() method to access elements of the collection). Individual items of the collection are of type Microsoft.Exchange.Data.ProxyAddressTemplate or Microsoft.Exchange.Data.SmtpProxyAddressTemplate.

To add or remove items from the collection, you use the normal methods available to collections – that is, the Add() and Remove() methods. However, to use the Add() or Remove() methods, you need to have an object of type Microsoft.Exchange.Data.ProxyAddressTemplate or of type Microsoft.Exchange.Data.SmtpProxyAddressTemplate.

How the heck do you get one of those?

In general, with .Net objects, you create objects using a constructor. In PowerShell, that translates to the New-Object cmdlet. So my normal process is to begin by using Google/Bing to search for the object name. The first or second hit is usually the MSDN web page that describes the class. Then, I read the page, find the appropriate constructor and bam! I am done. This is the method I followed in an earlier article: More Multi-valued Parameters in PowerShell (SourceTransportServers in Set-SendConnector).

But it doesn’t always work. I refer you back to the first paragraph. Many Exchange objects are poorly documented and the documentation that is present is often wrong. As an exercise, I invite you to Google/Bing the objects we are interested in today: Microsoft.Exchange.Data.ProxyAddressTemplate or Microsoft.Exchange.Data.SmtpProxyAddressTemplate. Read the MSDN web pages for the class, especially the page on constructors.

Once you’ve done that reading, you’ll try this:

$template = New-Object Microsoft.Exchange.Data.ProxyAddressTemplate(‘smtp’, ‘@example.com’)

which will not work. Then you’ll try:

$template = New-Object Microsoft.Exchange.Data.ProxyAddressTemplate(‘smtp:@example.com’)

which also will not work. Then you’ll try to get a raw object:

$template = New-Object Microsoft.Exchange.Data.ProxyAddressTemplate

which again will not work.

Then, if you are like me, you’ll spend the next couple of hours banging your head against the wall getting nowhere. After sleeping on the problem, I did come up with a very unsatisfying work-around. Since, in this case, we are trying to take a “currently enabled” object and make it disabled, we should be able to use a reference to the existing object. That solution looks like the below:

	$template = '@example.com'

	$policy = Get-EmailAddressPolicy 'Default Policy'

	$EnabledAddresses  = $policy.EnabledEmailAddressTemplates
	$DisabledAddresses = $policy.DisabledEmailAddressTemplates

	$objTemplate = $null

	foreach ($addr in $EnabledAddresses)
	{
		if ( $addr.AddressTemplateString -ieq $template )
		{
			"found $template"
			$objTemplate = $addr
			break
		}
		"skipped " + $addr.AddressTemplateString
	}

	if ( $objTemplate )
	{
		$EnabledAddresses.Remove( $objTemplate )
		$DisabledAddresses.Add( $objTemplate )

		$policy |
			Set-EmailAddressPolicy -EnabledEmailAddressTemplates $EnabledAddresses `
					       -DisabledEmailAddressTemplates $DisabledAddresses
	}

That, however, is a kludgey hack. In the above solution, we are using a reference to an existing object to change the policies. That will not work in many use-cases; for example where you want to add a new address template, whether enabled or disabled. You will find that you can’t modify the objects returned from Get-EmailAddressPolicy – they are marked read-only.

So, I asked some people who know a lot more about PowerShell than I. Shay Levy, a PowerShell MVP, pointed out that I was making it too hard and that this works:

$objTemplate = [Microsoft.Exchange.Data.ProxyAddressTemplate]’smtp:@example.com’

So, I slapped myself upside the head and said “Of course! ::Parse() is also a hidden constructor!” What that means, in English, is that the above statement is equivalent to:

$objTemplate = [Microsoft.Exchange.Data.ProxyAddressTemplate]::Parse( ‘smtp:@example.com’ )

and to:

$objTemplate = ‘smtp:@example.com’ -as [Microsoft.Exchange.Data.ProxyAddressTemplate]

PowerShell has a built-in capability, using a static method on an object, to take a string and attempt to coerce that string into the named object. In this case, a Microsoft.Exchange.Data.ProxyAddressTemplate. That static method must be named Parse() for PowerShell to automatically use the capability. You can see the static methods on this class using the below command:

[Microsoft.Exchange.Data.ProxyAddressTemplate] | Get-Member -static

Oisin Grehan, another PowerShell MVP, also pointed out that this is a perfect opportunity to use a filter. A filter is a PowerShell feature, somewhat uncommonly used, for processing objects in the pipeline. For example, to define a couple of useful filters:

	filter ConvertTo-ProxyAddressTemplate
	{
		$_ -as [Microsoft.Exchange.Data.ProxyAddressTemplate]
	}

	filter ConvertTo-SmtpProxyAddressTemplate
	{
		$_ -as [Microsoft.Exchange.Data.SmtpProxyAddressTemplate]
	}

These filters would take input from the pipeline and convert the pipeline input into the desired objects. For example:

	[PS] C:\S>'@example.com', '@sub1.example.com', '@sub2.example.com' | ConvertTo-SmtpProxyAddressTemplate

	AddressTemplateString      : @example.com
	ProxyAddressTemplateString : smtp:@example.com
	Prefix                     : SMTP
	IsPrimaryAddress           : False
	PrefixString               : smtp

	AddressTemplateString      : @sub1.example.com
	ProxyAddressTemplateString : smtp:@sub1.example.com
	Prefix                     : SMTP
	IsPrimaryAddress           : False
	PrefixString               : smtp

	AddressTemplateString      : @sub2.example.com
	ProxyAddressTemplateString : smtp:@sub2.example.com
	Prefix                     : SMTP
	IsPrimaryAddress           : False
	PrefixString               : smtp

So, given what we know now, what does the script look like? There are some similarities, but now it doesn’t feel like a hack and it is much easier to read and understand:

	$objTemplate = '@example.com' | ConvertTo-SmtpProxyAddressTemplate

	$policy = Get-EmailAddressPolicy 'Default Policy'

	$EnabledAddresses  = $policy.EnabledEmailAddressTemplates
	$DisabledAddresses = $policy.DisabledEmailAddressTemplates

	$EnabledAddresses.Remove( $objTemplate )
	$DisabledAddresses.Add( $objTemplate )

	$policy |
		Set-EmailAddressPolicy -EnabledEmailAddressTemplates $EnabledAddresses `
				       -DisabledEmailAddressTemplates $DisabledAddresses

So, what is the lesson learned? Don’t forget about ::Parse()! And, as almost always, in PowerShell there is often more than one way to skin a cat.

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter: @EssentialExch

Simplifying Life for Exchange Authors

Another Exchange MVP, Pat Richard, posted on Facebook yesterday: It would be nice to be able to import a list of all Exchange related PowerShell cmdlets into the Word and Outlook dictionaries.

Being the helpful guy that I am, I responded: You can do that in one-line of PowerShell.

(That being a famous quote from Jeff Snover, one of the main men behind PowerShell at Microsoft.)

Then I started thinking about it. And well, you CAN do it in one line of PowerShell. It just depends on how clever you want to get and how long a line you are willing to type! Regardless, you should only have to do it once.

Now, be aware, I’m running Office 2010 – file locations might be somewhat different for you.

Let’s build that one liner.

First thing was to find where Word 2010 stored custom words added to the dictionary. F1 (Help) in Word gave me that – CUSTOM.DIC.

Second was to locate the physical file:

PS C:\Users\Administrator> cd \
PS C:\> cmd /c dir custom.dic /s
Volume in drive C has no label.
Volume Serial Number is D888-0076

Directory of C:\Users\Administrator\AppData\Roaming\Microsoft\UProof

05/10/2010 08:50 AM 11,902 CUSTOM.DIC
1 File(s) 11,902 bytes

Total Files Listed:
1 File(s) 11,902 bytes
0 Dir(s) 77,055,504,384 bytes free
PS C:\>

Ah…good. So the filename is:

join-path C:\Users\Administrator\AppData\Roaming\Microsoft\UProof CUSTOM.DIC

or just

C:\Users\Administrator\AppData\Roaming\Microsoft\UProof\CUSTOM.DIC

If we examine the file, we see it’s a simple Unicode file containing one token/symbol per line.

How do we get the list of Exchange commands? Get-ExCommand, of course. But…if you aren’t running the command on an Exchange server or on a workstation where the Exchange Management Tools are installed (e.g., you are using remote PowerShell to connect to an Exchange server), you won’t have Get-ExCommand. It’s a non-exported function. However, you WILL have the Exchange module you can use. So, to get the Exchange commands:

Get-Command -module $global:importresults

Note that $global:importresults can potentially contain more than just the Exchange module. It is a list of all imported modules.

We notice that this returns more than we want. We really just want imported functions and cmdlets that have valid names. So:

Get-Command -module $global:importresults | ?
{ ($_.CommandType -eq “cmdlet” -or $_.CommandType -eq “function”) -and $_.Name -notmatch “:” }

That gives us our valid Exchange cmdlets and functions! However, all we are interested in is the name of the function; not all the other garbage:

Get-Command -module $global:importresults | ?
{ ($_.CommandType -eq “cmdlet” -or $_.CommandType -eq “function”) -and $_.Name -notmatch “:” } |
Select Name

Excellent. Now we have the names we want and we know where we want them to go! So let’s save them:

Get-Command -module $global:importresults | ?
{ ($_.CommandType -eq “cmdlet” -or $_.CommandType -eq “function”) -and $_.Name -notmatch “:” } |
Select Name |
Out-File -Append C:\Users\Administrator\AppData\Roaming\Microsoft\UProof\CUSTOM.DIC

or, since we know that’s the per-user AppData folder:

PS C:\> $env:AppData
C:\Users\Administrator\AppData\Roaming
PS C:\>

we can also say:

Get-Command -module $global:importresults | ?
{ ($_.CommandType -eq “cmdlet” -or $_.CommandType -eq “function”) -and $_.Name -notmatch “:” } |
Select Name |
Out-File -Append (join-path $env:AppData Microsoft\UProof\CUSTOM.DIC)

And there you have it. A LONG one-liner, but a one-liner nonetheless.

This does assume that you are running this on the same workstation where the custom dictionary resides.

This procedure should be generalizable (is that a word?) for any module.

Pat and I both worked on this going back and forth on FB. An interesting way to develop a script!

Until next time…

If there are things you would like to see written about, please let me know.

P.S. Pat went on and further developed the script to automatically choose an Exchange server, connect to it, and update the CUSTOM.DIC. To see that, visit his website.


Follow me on twitter: @EssentialExch

Exchange 2010 Gotcha – #1

As I work through migrations to Exchange Server 2010 with my various clients, I’m developing a list of Exchange 2010 “gotchas”. Not necessarily things that are earth-shattering, but do have the potential to be surprising to administrators.

Gotcha #1 revolves around the fact that an Offline Address Book is no longer automatically specified for a new mailbox database. Surprise!

So, just like most other things that require specific processes in Exchange, you should develop a list of items to be performed when you create a new mailbox database, whether you use the Exchange Management Console or the Exchanage Management Shell. Consider this as a list:

  1. Create the database, specifying an individual folder for the database (e.g., E:\DB-Zippy\Zippy.edb) and an individual folder for the log files (e.g., E:\Logs-Zippy). If you are using the EMC, and you do not want to take the default paths, you will have to type the entire path into the path fields (there is no browse button). As a suggestion, using Windows Explorer, first browse to the path where you want to put the files, and copy them into the clipboard, then you can paste them directly into the fields in EMC (or into the EMS, where you always have to enter the entire path).
  2. Set the Offline Address Book (OAB). If you are using the EMS, you can specify the OAB (e.g., “\Default Offline Address Book”) when you create the database. When using EMC, you’ll have to open the property sheet for the mailbox database, click the Client Settings tab, and select the address book via the Browse… button. You should also take this opportunity to verify that the Public Folder database is a valid public folder database (if you still have public folders in your environment).
  3. Set the Journal Recipient. You cannot specify the Journal Recipient during creation when using either EMC or EMS. With EMC, you’ll have to open the property sheet for the mailbox database, click the Maintenance tab, check the box to select the journal recipient, and browse for the particular user.
  4. Set external permissions. If you are using Cisco Unity or RIM’s BlackBerry Server (Enterprise, Professional, or Enterprise Express), then you’ll need to set additional permissions to allow those software packages to access mailboxes contained in this mailbox database. There is no mechanism for performing this operation using the EMC. For BlackBerry, this is the relevant command, executed from the EMS (assuming that your BlackBerry administrative user is named BESAdmin):

Get-MailboxDatabase | Add-ADPermission -User BESAdmin -AccessRights ExtendedRight -ExtendedRights Receive-As, Send-As, ms-Exch-Store-Admin

Obviously, there are additional parameters that can be set. However, these four meet the needs of all my clients and probably will work for you too!

Watch this blog for more “Exchange 2010 gotchas”.

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter: @EssentialExch

The Experts Conference – TEC’2010

For the third time, I’ll be speaking at the upcoming The Experts Conference, sponsored by Quest.

I’ll be discussing using Exchange Web Services (EWS) from PowerShell — and for some reason, my abstract isn’t yet posted on the conference website! I need to get that corrected.

TEC’2010 is being held in Los Angeles, CA this year, from April 25 – 28. The Exchange track will have it’s strongest expert content ever, with MVPs and Microsoft personnel from all over the United States and Europe. Of course, as always, TEC’2010 will have a huge Directory Services and Identity Management track and is introducing a SharePoint track.

See the official TEC’2010 website for more information.

I hope you’ll come join me and many other people at TEC’2010. It’s a great time with lots of talk-time with some of the best folks in the business.

Until next time…

If there are things you would like to see written about, please let me know


Follow me on twitter: @EssentialExch

Getting the Contents of an Active Directory Integrated DNS Zone, Version 2

In June of 2009, I published the first version of Getting the Contents of an Active Directory Integrated DNS Zone. Shortly after that, Chris Dent (chris at highorbit dot co dot uk) published a blog post clarifying the format of the dnsRecord attribute. Most of the time, the difference between the “correct” format and what I had deduced had no effect. But, I recently had to go back to this project and I needed to decode more DNS resource records. Now, using the proper format became important.

Thus, version 2 of the utility was born. I now process A, AAAA, MX, PTR, CNAME, SOA, NS, TXT, SRV, and HINFO resource records.

I hope you’ll enjoy this update. Now, if I can figure out how to determine lame delegations in PowerShell, I can write a PowerShell version of dnswalk!

##
## dns-dump.ps1
##
## Michael B. Smith
## michael at smithcons dot com
## http://TheEssentialExchange.com/blogs/michael
## May/June, 2009
## Updated December, 2009 adding many add'l record types.
##
## Use as you wish, no warranties expressed, implied or explicit.
## Works for me, may not for you.
## If you use it, I would appreciate an attribution.
##
## Thanks to Chris Dent, chris at highorbit dot co dot uk
## for some clarification on the precise format of the
## dnsRecord attribute. See his blog post on the topic at
## http://www.highorbit.co.uk/?p=1097
##

Param(
	[string]$zone,
	[string]$dc,
	[switch]$csv,
	[switch]$help
)

function dumpByteArray([System.Byte[]]$array, [int]$width = 9)
{
	## this is only used if we run into a record type
	## we don't understand.

	$hex = ""
	$chr = ""
	$int = ""

	$i = $array.Count
	"Array contains {0} elements" -f $i
	$index = 0
	$count = 0
	while ($i-- -gt 0)
	{
		$val = $array[$index++]

		$hex += ("{0} " -f $val.ToString("x2"))

		if ([char]::IsLetterOrDigit($val) -or 
		    [char]::IsPunctuation($val)   -or 
		   ([char]$val -eq " "))
		{
			$chr += [char]$val
		}
		else
		{
			$chr += "."
		}

		$int += "{0,4:N0}" -f $val

		$count++
		if ($count -ge $width)
		{
			"$hex $chr $int"
			$hex = ""
			$chr = ""
			$int = ""
			$count = 0
		}		
	}

	if ($count -gt 0)
	{
		if ($count -lt $width)
		{
			$hex += (" " * (3 * ($width - $count)))
			$chr += (" " * (1 * ($width - $count)))
			$int += (" " * (4 * ($width - $count)))
		}

		"$hex $chr $int"
	}
}

function dwordLE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert four consecutive bytes in $arr into a
	## 32-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for little-endian values.

	$res = $arr[$startIndex + 3]
	$res = ($res * 256) + $arr[$startIndex + 2]
	$res = ($res * 256) + $arr[$startIndex + 1]
	$res = ($res * 256) + $arr[$startIndex + 0]

	return $res
}

function dwordBE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert four consecutive bytes in $arr into a
	## 32-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for big-endian values.

	$res = $arr[$startIndex]
	$res = ($res * 256) + $arr[$startIndex + 1]
	$res = ($res * 256) + $arr[$startIndex + 2]
	$res = ($res * 256) + $arr[$startIndex + 3]

	return $res
}

function wordLE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert two consecutive bytes in $arr into a
	## 16-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for little-endian values.

	$res = $arr[$startIndex + 1]
	$res = ($res * 256) + $arr[$startIndex]

	return $res
}

function wordBE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert two consecutive bytes in $arr into a
	## 16-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for big-endian values.

	$res = $arr[$startIndex]
	$res = ($res * 256) + $arr[$startIndex + 1]

	return $res
}

function decodeName([System.Byte[]]$arr, [int]$startIndex)
{
	## names in DNS are stored in two formats. one
	## format contains a single name and is what we
	## called "simple string" in the old days. the
	## first byte of a byte array contains the length
	## of the string, and the rest of the bytes in 
	## the array are the data in the string.
	##
	## a "complex string" is built from simple strings.
	## the complex string is prefixed by the total
	## length of the complex string in byte 0, and the
	## total number of segments in the complex string
	## in byte 1, and the first simple string starts 
	## (with its length byte) in byte 2 of the complex
	## string.

	[int]$totlen   = $arr[$startIndex]
	[int]$segments = $arr[$startIndex + 1]
	[int]$index    = $startIndex + 2

	[string]$name  = ""

	while ($segments-- -gt 0)
	{
		[int]$segmentLength = $arr[$index++]
		while ($segmentLength-- -gt 0)
		{
			$name += [char]$arr[$index++]
		}
		$name += "."
	}

	return $name
}

function analyzeArray([System.Byte[]]$arr, [System.Object]$var)
{
	$nameArray = $var.distinguishedname.ToString().Split(",")
	$name = $nameArray[0].SubString(3)

	## RData Length is the length of the payload in bytes (that is, the variable part of the record)
	## Truth be told, we don't use it. The payload starts at $arr[24]. If you are ever concerned
	## about corrupt data and running off the end of $arr, then you need to verify against the RData
	## Length value.
	$rdataLen = wordLE $arr 0

	## RData Type is the type of the record
	$rdatatype = wordLE $arr 2

	## the serial in the SOA where this item was last updated
	$updatedAtSerial = dwordLE $arr 8

	## TimeToLive
	$ttl = dwordBE $arr 12

	## $unknown = dword $arr 16

	## timestamp of when the record expires, or 0 means "static"
	$age = dwordLE $arr 20
	if ($age -ne 0)
	{
		## hours since January 1, 1601 (start of Windows epoch)
		## there is a long-and-dreary way to do this manually,
		## but get-date makes it trivial to do the conversion.
		$timestamp = ((get-date -year 1601 -month 1 -day 1 -hour 0 -minute 0 -second 0).AddHours($age)).ToString()
	}
	else
	{
		$timestamp = "[static]"
	}

	if ($rdatatype -eq 1)
	{
		# "A" record
		$ip = "{0}.{1}.{2}.{3}" -f $arr[24], $arr[25], $arr[26], $arr[27]

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "A", $ip
	}
	elseif ($rdatatype -eq 2)
	{
		# "NS" record
		$nsname = decodeName $arr 24

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "NS", $nsname
	}
	elseif ($rdatatype -eq 5)
	{
		# CNAME record
		# canonical name or alias

		$alias = decodeName $arr 24

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "CNAME", $alias
	}
	elseif ($rdatatype -eq 6)
	{
		# "SOA" record
		# "Start-Of-Authority"

		$nslen = $arr[44]
		$priserver = decodeName $arr 44
		$index = 46 + $nslen

		# "Primary server: $priserver"

		##$index += 1
		$resparty = decodeName $arr $index

		# "Responsible party: $resparty"

		# "TTL: $ttl"
		# "Age: $age"

		$serial = dwordBE $arr 24
		# "Serial: $serial"

		$refresh = dwordBE $arr 28
		# "Refresh: $refresh"

		$retry = dwordBE $arr 32
		# "Retry: $retry"

		$expires = dwordBE $arr 36
		# "Expires: $expires"

		$minttl = dwordBE $arr 40
		# "Minimum TTL: $minttl"

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}"

			$formatstring -f $name, $timestamp, $ttl, `
				"SOA", $priserver, $resparty, `
				$serial, $refresh, $retry, `
				$expires, $minttl
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}"

			$formatstring -f $name, $timestamp, $ttl, "SOA"
			(" " * 32) + "Primary server: $priserver"
			(" " * 32) + "Responsible party: $resparty"
			(" " * 32) + "Serial: $serial"
			(" " * 32) + "TTL: $ttl"
			(" " * 32) + "Refresh: $refresh"
			(" " * 32) + "Retry: $retry"
			(" " * 32) + "Expires: $expires"
			(" " * 32) + "Minimum TTL (default): $minttl"
		}
	}
	elseif ($rdatatype -eq 12)
	{
		# "PTR" record

		$ptr = decodeName $arr 24

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "PTR", $ptr
	}
	elseif ($rdatatype -eq 13)
	{
		# "HINFO" record

		[string]$cputype = ""
		[string]$ostype  = ""

		[int]$segmentLength = $arr[24]
		$index = 25

		while ($segmentLength-- -gt 0)
		{
			$cputype += [char]$arr[$index++]
		}

		$index = 24 + $arr[24] + 1
		[int]$segmentLength = $index++

		while ($segmentLength-- -gt 0)
		{
			$ostype += [char]$arr[$index++]
		}

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4},{5}"
		}

		$formatstring -f $name, $timestamp, $ttl, "HINFO", $cputype, $ostype
	}
	elseif ($rdatatype -eq 15)
	{
		# "MX" record

		$priority = wordBE $arr 24
		$mxhost   = decodeName $arr 26

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}  {5}"
		}

		$formatstring -f $name, $timestamp, $ttl, "MX", $priority, $mxhost
	}
	elseif ($rdatatype -eq 16)
	{
		# "TXT" record

		[string]$txt  = ""

		[int]$segmentLength = $arr[24]
		$index = 25

		while ($segmentLength-- -gt 0)
		{
			$txt += [char]$arr[$index++]
		}

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "TXT", $txt

	}
	elseif ($rdatatype -eq 28)
	{
		# "AAAA" record

		### yeah, this doesn't do all the fancy formatting that can be done for IPv6

		$str = ""
		for ($i = 24; $i -lt 40; $i+=2)
		{
			$seg = wordBE $arr $i
			$str += ($seg).ToString('x4')
			if ($i -ne 38) { $str += ':' }
		}

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "AAAA", $str
	}
	elseif ($rdatatype -eq 33)
	{
		# "SRV" record

		$port   = wordBE $arr 28
		$weight = wordBE $arr 26
		$pri    = wordBE $arr 24

		$nsname = decodeName $arr 30

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3} {4} {5}"
		}

		$formatstring -f `
			$name, $timestamp, `
			$ttl, "SRV", `
			("[" + $pri.ToString() + "][" + $weight.ToString() + "][" + $port.ToString() + "]"), `
			$nsname
	}
	else
	{
		$name
		"RDataType $rdatatype"
		$var.distinguishedname.ToString()
		dumpByteArray $arr
	}

}

function processAttribute([string]$attrName, [System.Object]$var)
{
	$array = $var.$attrName.Value
####	"{0} contains {1} rows of type {2} from {3}" -f $attrName, $array.Count, $array.GetType(), $var.distinguishedName.ToString()

	if ($array -is [System.Byte[]])
	{
####		dumpByteArray $array
		" "
		analyzeArray $array $var
		" "
	}
	else
	{
		for ($i = 0; $i -lt $array.Count; $i++)
		{
####			dumpByteArray $array[$i]
			" "
			analyzeArray $array[$i] $var
			" "
		}
	}
}

function usage
{
"
.\dns-dump -zone  [-dc ] [-csv] |
	   -help

dns-dump will dump, from Active Directory, a particular named zone. 
The zone named must be Active Directory integrated.

Zone contents can vary depending on domain controller (in regards
to replication and the serial number of the SOA record). By using
the -dc parameter, you can specify the desired DC to use. Otherwise,
dns-dump uses the default DC.

Usually, output is formatted for display on a workstation. If you
want CSV (comma-separated-value) output, specify the -csv parameter.
Use out-file in the pipeline to save the output to a file.

Finally, to produce this helpful output, you can specify the -help
parameter.

This command is basically equivalent to (but better than) the:

	dnscmd /zoneprint 
or
	dnscmd /enumrecords  '@'

commands.

Example 1:

	.\dns-dump -zone essential.local -dc win2008-dc-3

Example 2:

	.\dns-dump -help

Example 3:

	.\dns-dump -zone essential.local -csv |
            out-file essential.txt -encoding ascii

	Note: the '-encoding ascii' is important if you want to
	work with the file within the old cmd.exe shell. Otherwise,
	you can usually leave that off.
"
}

	##
	## Main
	##

	if ($help)
	{
		usage
		return
	}

	if ($args.Length -gt 0)
	{
		write-error "Invalid parameter specified"
		usage
		return
	}

	if (!$zone)
	{
		throw "must specify zone name"
		return
	}

	$root = [ADSI]"LDAP://RootDSE"
	$defaultNC = $root.defaultNamingContext

	$dn = "LDAP://"
	if ($dc) { $dn += $dc + "/" }
	$dn += "DC=" + $zone + ",CN=MicrosoftDNS,CN=System," + $defaultNC

	$obj = [ADSI]$dn
	if ($obj.name)
	{
		if ($csv)
		{
			"Name,Timestamp,TTL,RecordType,Param1,Param2"
		}

		#### dNSProperty has a different format than dNSRecord
		#### processAttribute "dNSProperty" $obj

		foreach ($record in $obj.psbase.Children)
		{
			####	if ($record.dNSProperty) { processAttribute "dNSProperty" $record }
			if ($record.dnsRecord)   { processAttribute "dNSRecord"   $record }
		}
	}
	else
	{
		write-error "Can't open $dn"
	}

	$obj = $null

Until next time…

If there are things you would like to see written about, please let me know


Follow me on twitter: @EssentialExch

Exchange Server 2010 – Administrative Access to All Mailboxes

In Exchange 2010, the storage group has disappeared. Instead, the properties of a database and of a storage group have merged – the result being referred to as a database. Effectively, a database has been promoted to be as important as a storage group used to be.

You may could have predicted this coming from changes which happened in Exchange 2007, as a number of features required that a storage group only have a single database contained within those storage groups.

Regardless of which, a mailboxdatabase is unique within an Exchange 2010 organization. That means you can no longer have a mailboxdatabase named “Mailbox Database” or “Mailbox (servername)” on each and every server within your Exchange organization. Instead, each and every mailboxdatabase name is unique. This is guaranteed by a many-digit number suffixed to the end of a mailbox database’s name.

This does simplify some aspects of administration – instead of having to specify server\storage-group\database in order to name a specific database, you can now specify simply the database name. However, the name of that database may be something like “Mailbox Database 1015374940” (which is the name of the mailbox database hosting my production domain). That is somewhat more challenging to remember. Just somewhat. HAH.

One of the changes involved in moving databases to be organizational objects instead of server objects makes it practical to (again – after skipping Exchange 2007) allow a single user or group administrative access to all Exchange 2007 mailboxes.

Of course, this can be done from the GUI – however, the GUI you must use is LDP.exe or ADSIEdit.msc – not the Exchange Management Console (EMC).

However, this is probably easier to do from the Exchange Management Shell (EMS), given that you know a couple of key facts: the distinguishedname of your Active Directory domain and any of three formats for a user/group you want to allow this access.

Note that allowing Administrative Access to all mailboxes can be tracked by logging – but only if that logging is enabled – and that logging is not enabled by default. Also note that there may be legal issues associated with allowing specific users or groups access to all mailboxes in your organization – I recommend that every organization have a information access and security policy that includes corporate access and use of electronic mail. Finally, this information is provided for instructional purposes and I accept no liability for providing this information or to any use to which it may be put.

Now that I’ve covered my rear….

If, for example, your forest is named named example.com, then the distinguished name of that forest is DC=example,DC=com. If your forest is named SBS.Example.Local, then the distinguishedname of the forest is DC=SBS,DC=Example,DC=Local. Now, remember that. 🙂

In terms of specifying a user or group name that you are going to provide access, you have three possible formats:

NetBIOS-domain-name\principal-name

Active-Directory-forest-name/container-or-organizational-unit/principal-name

CN=principal-name,OU=organizational-unit,DC=example,DC=local

For example, if your Active Directory forest name is example.local and the NetBIOS domain name is EXAMPLE, and the security principal is named TEST and that principal is located in the Users container, you would have these examples:

EXAMPLE\TEST

example.local/Users/TEST

CN=TEST,CN=Users,DC=example,DC=local

Finally, using the above example, you would have this PowerShell command:

Add-AdPermission –Identity “CN=Databases,CN=Exchange Administrative Group (FYDIBOHF23SPDLT),CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=example,DC=local” –User EXAMPLE\TEST –InheritedObjectType msExchPrivateMDB –extendedRights Receive-As –inheritanceType Descendents

Or, if we were to expand this out a little bit:

$principal = “EXAMPLE\Test”
$domain = “DC=example,DC=local”
$identity = “CN=Databases,” +
“CN=Exchange Administrative Group (FYDIBOHF23SPDLT),” +
“CN=Administrative Groups,CN=First Organization,” +
“CN=Microsoft Exchange,CN=Services,CN=Configuration,” +
$domain
Add-AdPermission –Identity $identity –User $principal `
–InheritedObjectType msExchPrivateMDB `
–extendedRights Receive-As `
–inheritanceType Descendents

Until next time…

If there are things you would like to see written about, please let me know!


Follow me on twitter: @EssentialExch

msExchTargetBridgeheadServersDN and msExchSourceBridgeheadServersDN Errors with Routing Group Connectors

I’m working with a client this week on migrating them from Exchange Server 2003 to Exchange Server 2007.

Their Exchange 2003 environment is a weird one – a A/A/A/P/P cluster. That’s right – Active/Active/Active/Passive/Passive. Whoever sold them that solution made some serious money! 🙂

I’m moving them to a more standard solution – dual front end servers (CAS + HT) and a CCR backend for the mailboxes. Obviously, we are going to have coexistence for some period of time – there are thousands of mailboxes involved.

One of the standard steps in a co-existence scenario is to create a inter-server-version (InterOp) routing group connector (RGC) which allows the Exchange 2003 environment to communicate (i.e., transfer email) with the Exchange 2007 environment.

Today I finished setting up the CAS+HT servers and the CCR servers and began to set up the InterOp RGC. I used the same syntax I’ve used with dozens of other migrations:

New-RoutingGroupConnector -name Interop-RGC -SourceTransportServers server1, server2, server3 -TargetTransportServers ExchCas1, ExchCas2 -BiDirectional $true -PublicFolderReferralsEnabled $true

And… crap. It bombs.

WTF?!?!?

New-RoutingGroupConnector : Active Directory operation failed on dc3.example.com. This error is not retriable. Additional information: The name reference is invalid.
This may be caused by replication latency between Active Directory domain controllers.
Active directory response: 000020B5: AtrErr: DSID-03152392, #1:
0: 000020B5: DSID-03152392, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 315c30e2 (msExchTargetBridgeheadServersDN)
At line:1 char:26
+ new-routinggroupconnector <<<< -domaincontroller dc3.example.com -identity "Exchange Administrative Group (FYDIBOHF23SPDLT)\Exchange Routing Group (DWBGZMFD01QNBJR)\InterOp-RGC" -sourcetransportservers server1, server2, server3 -targettransportservers ExchCas1, ExchCas2 -BiDirectional $true -PublicFolderReferralsEnabled $true

Well, it has nothing whatsoever to do with “replication latency between Active Directory domain controllers”. I spent far too long heading down that path before I started googling around to see what else might be on the “Interwebs” about this issue.

It turns out that Exchange 2003 assigns indexes to each SMTP Virtual Server on a computer. The New-RoutingGroupConnector command will only connect to SMTP Virtual Servers that have a index value of 1. Is this a bug? Absolutely. Has it been reported? Absolutely. Has it been fixed? Absolutely not. Note that the same error can occur with the attribute msExchangeSourceBridgeheadServersDN – depends on how you construct the cmdlet.

As of the Exchange 2010 beta, this issue still appears.

In a A/A/A/P/P cluster, the indexes for the SMTP Virtual Servers go 1, 2, 3. This means that you can only specify a single SMTP Virtual Server in the New-RoutingGroupConnector cmdlet, and you can only specify the SMTP Virtual Server for the Exchange Server that has the index of 1. This ALSO means that, for the InterOp-RGC to work, that the Exchange 2003 server with the index of 1 must be the routing group master!

Finding those things out cost me several hours of my life. I hope this blog post helps you avoid the same timesink I had!

Until next time…

If there are things you would like to see written about, please let me know!


Follow me on twitter: @EssentialExch

Removing Old Emailaddresses/ProxyAddresses in Exchange 2007

I have a client who has multiple business units. Each business unit is placed into its own Organizational Unit (OU) in Active Directory and the business unit has its own unique Email Address Policy (EAP) that (using a RecipientFilter) assigns specific email addresses to all users in that OU.

The client has changed the name of one of their business units, and as of today, the old email addresses were to “go away”.

There is no in-built functionality for automatically removing email addresses. You can modify the EAP to specify a new address template as the default, but even when you remove the old address template, that email address is not removed from the user object (Exchange has worked this way since at least Exchange 2000, Exchange 5.5 used a somewhat different model).

What do you do? Well, you use PowerShell of course! This “one-liner” does the job:

get-mailbox -organizationalunit ad.example.com/BusinessUnitOU/users |% { $a = $_.emailaddresses; $b = $_.emailaddresses; foreach($e in $a) { if ($e.tostring() -match "old-email-domain") { $b -= $e; } } ; $_ | set-mailbox -emailaddresses $b }

You can enter this as a single line (and I did). However, the command is easier to understand when you separate it out, as shown below:

get-mailbox -organizationalunit ad.example.com/BusinessUnitOU/users |% 
    { 
        $a = $_.emailaddresses; 
        $b = $_.emailaddresses; 
        foreach ($e in $a) 
        { 
            if ($e.tostring() -match "old-email-domain") 
            { 
                $b -= $e; 
            } 
         }

         $_ | set-mailbox -emailaddresses $b 
    }


The Get-Mailbox command specifies the unique organizational unit that we are looking at. In this case, Get-Mailbox will return a mailbox object for all mailboxes that exist within the ad.example.com/BusinessUnitOU/users organizational unit. Note that I have not used the distinguishedName format for the organizational unit, but instead the X.500 format. Either is acceptable. However, the X.500 format only requires quoting when you have spaces in your organizational unit names. The distinguishedName format must always be quoted or escaped due to its use of commas (and how PowerShell interprets commas in open text strings).

The “|%” syntax indicates that the output of the Get-Mailbox command is to be evaluated, for each object returned, based on the following script block. In this case, the script block begins on the next line with “{” in column 4 and terminates 12 lines later with a “}” in column 4.

First, we make two copies of the email address collection for the mailbox that is returned. Note that setting “$b = $a” would have a different behavior! Instead of creating a copy of the collection, it would create a reference – that is, $b would point to the same collection as $a, and the following loop would not work properly, as described in the next paragraph.

Next, we evaluate each email address contained within the email address collection ($a). If a particular email address matches the old email address domain, we remove that particular email address from the second collection ($b). If we removed it from the original collection ($a), then the foreach loop would fail. Not a good thing to have happen – which is why we have two collections!

After examining all of the email addresses in the collection, we pipe the mailbox object to Set-Mailbox using the new/adjusted collection. And we are done!

But you don’t need to understand the script in order to use the script. To use the script, replace the -organizationalunit parameter with the one you are wanting to change (or completely remove the parameter if you want to affect all mailboxes). Next, replace old-email-domain with the domain you are want to remove. That’s all it takes.

Until next time…

If there are things you would like to see written about, please let me know!

[Edit March 29, 2012]

Mike Crowley, another Exchange MVP, makes the following comment that I thought I would share:

Michael, this was very helpful, thanks again! I
needed to do this for one of my customers, however the address that needed to
‘go away’ was a parent domain of one that needed to stay (e.g. ‘domain.com’
needed to go away, but ‘subdomain.domain.com’ needed to stay). I modified
your script and ran the below:

get-mailbox -organizationalunit
ad.domain.com/BusinessUnitOU/users | % { $a = $_.emailaddresses; $b =
$_.emailaddresses; foreach($e in $a) { if (($e.tostring() -match
“domain.com”) -and ($e.tostring() -notmatch
“subdomain.domain.com”)) { $b -= $e; } } ; $_ | set-mailbox
-emailaddresses $b }

-Mike


Follow me on twitter: @EssentialExch

Removing Old Emailaddresses/ProxyAddresses in Exchange 2007

I have a client who has multiple business units. Each business unit is placed into its own Organizational Unit (OU) in Active Directory and the business unit has its own unique Email Address Policy (EAP) that (using a RecipientFilter) assigns specific email addresses to all users in that OU.

The client has changed the name of one of their business units, and as of today, the old email addresses were to “go away”.

There is no in-built functionality for automatically removing email addresses. You can modify the EAP to specify a new address template as the default, but even when you remove the old address template, that email address is not removed from the user object (Exchange has worked this way since at least Exchange 2000, Exchange 5.5 used a somewhat different model).

What do you do? Well, you use PowerShell of course! This “one-liner” does the job:

get-mailbox -organizationalunit ad.example.com/BusinessUnitOU/users |% { $a = $_.emailaddresses; $b = $_.emailaddresses; foreach($e in $a) { if ($e.tostring() -match "old-email-domain") { $b -= $e; } } ; $_ | set-mailbox -emailaddresses $b }

You can enter this as a single line (and I did). However, the command is easier to understand when you separate it out, as shown below:

get-mailbox -organizationalunit ad.example.com/BusinessUnitOU/users |% 
    { 
        $a = $_.emailaddresses; 
        $b = $_.emailaddresses; 
        foreach ($e in $a) 
        { 
            if ($e.tostring() -match "old-email-domain") 
            { 
                $b -= $e; 
            } 
         }

         $_ | set-mailbox -emailaddresses $b 
    }


The Get-Mailbox command specifies the unique organizational unit that we are looking at. In this case, Get-Mailbox will return a mailbox object for all mailboxes that exist within the ad.example.com/BusinessUnitOU/users organizational unit. Note that I have not used the distinguishedName format for the organizational unit, but instead the X.500 format. Either is acceptable. However, the X.500 format only requires quoting when you have spaces in your organizational unit names. The distinguishedName format must always be quoted or escaped due to its use of commas (and how PowerShell interprets commas in open text strings).

The “|%” syntax indicates that the output of the Get-Mailbox command is to be evaluated, for each object returned, based on the following script block. In this case, the script block begins on the next line with “{” in column 4 and terminates 12 lines later with a “}” in column 4.

First, we make two copies of the email address collection for the mailbox that is returned. Note that setting “$b = $a” would have a different behavior! Instead of creating a copy of the collection, it would create a reference – that is, $b would point to the same collection as $a, and the following loop would not work properly, as described in the next paragraph.

Next, we evaluate each email address contained within the email address collection ($a). If a particular email address matches the old email address domain, we remove that particular email address from the second collection ($b). If we removed it from the original collection ($a), then the foreach loop would fail. Not a good thing to have happen – which is why we have two collections!

After examining all of the email addresses in the collection, we pipe the mailbox object to Set-Mailbox using the new/adjusted collection. And we are done!

But you don’t need to understand the script in order to use the script. To use the script, replace the -organizationalunit parameter with the one you are wanting to change (or completely remove the parameter if you want to affect all mailboxes). Next, replace old-email-domain with the domain you are want to remove. That’s all it takes.

Until next time…

If there are things you would like to see written about, please let me know!

[Edit March 29, 2012]

Mike Crowley, another Exchange MVP, makes the following comment that I thought I would share:

Michael, this was very helpful, thanks again! I
needed to do this for one of my customers, however the address that needed to
‘go away’ was a parent domain of one that needed to stay (e.g. ‘domain.com’
needed to go away, but ‘subdomain.domain.com’ needed to stay). I modified
your script and ran the below:

get-mailbox -organizationalunit
ad.domain.com/BusinessUnitOU/users | % { $a = $_.emailaddresses; $b =
$_.emailaddresses; foreach($e in $a) { if (($e.tostring() -match
“domain.com”) -and ($e.tostring() -notmatch
“subdomain.domain.com”)) { $b -= $e; } } ; $_ | set-mailbox
-emailaddresses $b }

-Mike


Follow me on twitter: @EssentialExch