<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://theessentialexchange.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Essential Exchange</title><link>http://theessentialexchange.com/blogs/</link><description>&lt;i&gt;Your source for everything essential about &lt;br/&gt;Microsoft Exchange Server&lt;/i&gt;</description><dc:language>en-US</dc:language><generator>CommunityServer 2007.1 (Build: 20917.1142)</generator><item><title>Reporting on primary and secondary SMTP addresses on Exchange objects</title><link>http://theessentialexchange.com/blogs/michael/archive/2013/04/26/reporting-on-primary-and-secondary-smtp-addresses-on-exchange-objects.aspx</link><pubDate>Fri, 26 Apr 2013 19:17:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6991</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Most companies have a set of primary Internet domains (Exchange accepted domains) that they use to assign to users. However, a constant is that most companies also assign a secondary email address that has a domain which is identical for all of their users. That is true in my environment, and in that of all customers I have worked with in the past.&lt;/p&gt;
&lt;p&gt;However, Exchange tends to generate (especially if you have migrated from legacy Exchange versions) far more email addresses than that for any given Exchange object (user, group, contact).&lt;/p&gt;
&lt;p&gt;The script below reports on each user&amp;#39;s primary SMTP address, plus a secondary SMTP address whose domain is specified as a parameter.&lt;/p&gt;
&lt;p&gt;The script uses the Active Directory PowerShell module, which must be installed on the computer where this script is executed. The script does not use any Exchange specific features, thus the Exchange Management Shell is not required. I tested the script on Exchange 2010 and Exchange 2013, but it should work on Exchange 2007 as well.&lt;/p&gt;
&lt;p&gt;A couple of techniques worth noting are used here. First, instead of a function, I use a filter. A filter is a very special kind of a function optimized for working with pipelines of objects. Inputs from the pipeline are passed to the filter using $_.&lt;/p&gt;
&lt;p&gt;Second, I parse the proxyAddresses attribute. This attribute contains a list of all addresses that are assigned to a given user. It is an array (a collection) of all those addresses. Importantly, for each item in the collection, the address itself is prefixed by an &lt;em&gt;address type&lt;/em&gt;. For SMTP addresses, the address type is &amp;quot;smtp&amp;quot;. For FAX addresses, the address type is &amp;quot;fax&amp;quot;. For X500 addresses, the address type is &amp;quot;x500&amp;quot;. Etc. Also importantly, if the address type is capitalized, then a particular address is the &lt;em&gt;primar&lt;/em&gt;y address of that type - the default. If the address type is not capitalized, then the address is a &lt;em&gt;secondary&lt;/em&gt; address. There may be any number of secondary addresses of a particular type. There may be only one primary address of a particular type.&lt;/p&gt;
&lt;p&gt;In order to detect primary addresses, I use the &amp;quot;-ceq&amp;quot; operator in PowerShell. This is &amp;quot;cased equal&amp;quot;. That means that the case of the letters is significant. By default, comparisons in PowerShell are not case significant.&lt;/p&gt;
&lt;p&gt;Finally, Exchange objects will always have the proxyAddresses attribute populated. This fact is used to build LDAP query utilized to find Exchange objects.&lt;/p&gt;
&lt;p&gt;I hope you find this useful!&lt;/p&gt;
&lt;p&gt;&lt;font color="green"&gt;
&lt;blockquote&gt;&lt;pre&gt;##
## Get-PrimaryAndSecondary
##
## Michael B. Smith
## April, 2013
##
Param(
	[string]$secondaryDomain = &amp;quot;@TheEssentialExchange.com&amp;quot;
)

[int]$secondaryDomainLen = $secondaryDomain.Length

filter strip-Addresses
{
	$proxies = $_.proxyAddresses

	$primary   = &amp;quot;&amp;quot;
	$secondary = &amp;quot;&amp;quot;

	$object = &amp;quot;&amp;quot; | Select GivenName, Surname, sAMAccountName, PrimarySmtp, SecondarySmtp

	$object.GivenName      = $_.GivenName
	$object.SurName        = $_.SurName
	$object.sAMAccountName = $_.sAMAccountName

	foreach( $proxy in $proxies )
	{
		$len = $proxy.Length

		## note: &amp;quot;SMTP:&amp;quot;.Length == 5

		## note: The primary SMTP address has a CAPITALIZED &amp;quot;SMTP:&amp;quot; prefix
		## all secondary SMTP addresses have a lowercase &amp;quot;smtp:&amp;quot; prefix

		## note: any interesting secondary proxy address will be longer than 
		## &amp;quot;SMTP:&amp;quot;.Length + $secondaryDomainLen

		if( $len -gt 5 )
		{
			$prefix = $proxy.SubString( 0, 5 )
			$temp   = $proxy.SubString( 5 )	##strip off &amp;quot;smtp:&amp;quot;, if present

			if( $prefix -ceq &amp;quot;SMTP:&amp;quot; )
			{
				$primary = $temp
				if( $secondary.Length -gt 0 )
				{
					break   ## we have both primary and secondary, 
						## we don&amp;#39;t need to look any more
				}
			}
			elseif( $prefix -ceq &amp;quot;smtp:&amp;quot; -and $len -gt ( 5 + $secondaryDomainLen ) )
			{
				if( $temp.EndsWith( $secondaryDomain ) )
				{
					$secondary = $temp
					if( $primary.Length -gt 0 )
					{
						break   ## we have both primary and secondary, 
							## we don&amp;#39;t need to look any more
					}
				}
			}
		}
	}

	$object.PrimarySmtp   = $primary
	$object.SecondarySmtp = $secondary

	$object
}

Import-Module ActiveDirectory

Get-AdUser -LDAPFilter &amp;quot;(&amp;amp;(objectCategory=user)(proxyAddresses=*))&amp;quot; `
	-Properties GivenName, SurName, proxyAddresses -ResultSetSize $null | 
	strip-Addresses

&lt;/pre&gt;&lt;/blockquote&gt;&lt;/font&gt;
&lt;p&gt;Follow me on twitter, @essentialexch&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6991" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Determining the Exchange Version - without using Get-ExchangeServer - Update 2013</title><link>http://theessentialexchange.com/blogs/michael/archive/2013/04/11/determining-the-exchange-version-without-using-get-exchangeserver-update-2013.aspx</link><pubDate>Thu, 11 Apr 2013 23:20:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6990</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;This is an update of my post Determining the Exchange Version - without using Get-ExchangeServer, from April 25, 2012. Since then, Exchange 2013 has been released! I&amp;#39;ve had several requests from people who are not PowerShell scripters to update that function. So here it is. I have repeated the text from the prior below.&lt;/p&gt;
&lt;p&gt;If you write lots of Exchange scripts (as I do), and have several different versions of Exchange on which you need to run those scripts (as I do); you soon see the need for being able to determine what version of Exchange a particular server is running - and you may need to do this outside of the Exchange Management Shell.&lt;/p&gt;
&lt;p&gt;This may be necessary because of different behaviors that are required with the Exchange Management Shell depending on the version of Exchange. It also may be required because the version of Exchange pre-dates the Exchange Management Shell (i.e., Exchange 2003). As an additional rationale, your script may need to load the Exchange Management Shell and cannot do that properly without knowing the version of Exchange that is being targeted (the process differs between Exchange 2007 and Exchange 2010 and Exchange 2013).&lt;/p&gt;
&lt;p&gt;Thus, I&amp;#39;ve written a couple of functions that I wrap in a script to give me that information. Get-ExchangeServer, the function presented in this post, returns a simple object containing the name of the server examined, plus the version of Exchange that is running on that server. That is, in C pseudo-syntax:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;struct result {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; string Name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; string Version;&lt;br /&gt;}&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;If the result of the function is $null, then the version could not be determined and the server targeted either does not exist or is (very likely) not running Exchange. If the server does not exist (or the firewall on the server prevents remote management via WMI) then an error is displayed.&lt;/p&gt;
&lt;p&gt;The version information about Exchange is stored in the registry of each Exchange server. The script below shows some techniques for accessing the registry to obtain string (reg_sz) and 32-bit short integer (reg_dword) values while using PowerShell. Note that PowerShell has multiple mechanisms for accessing this information, including the so-called Registry Provider. I personally find using the WMI functions to be a bit easier to handle.&lt;/p&gt;
&lt;p&gt;You can include these functions directly into your PowerShell profile, or you can dot-source the function on an as-needed basis.&lt;/p&gt;
&lt;p&gt;Without further ado, enjoy!&lt;/p&gt;
&lt;p&gt;&lt;font color="green"&gt;&lt;pre&gt;
###
### Get-ExchangeVersion
###
### Return the version of the specified Exchange server
###
### 2013-04-11
###	Updated to support Exchange Server 2013 and E16
###

Set-StrictMode -Version 2.0

$HKCR = 2147483648
$HKCU = 2147483649
$HKLM = 2147483650

function RegRead
{
	Param(
		[string]$computer,
		[int64] $hive,
		[string]$keyName,
		[string]$valueName,
		[ref]   $value,
		[string]$type = &amp;#39;reg_sz&amp;#39;
	)

	[string]$fn = &amp;quot;RegRead:&amp;quot; ## function name

	try {
		$wmi = [wmiclass]&amp;quot;\\$computer\root\default:StdRegProv&amp;quot;
		if( $wmi -eq $null )
		{
			return 1
		}
	}
	catch {
		$error[0]
		write-error &amp;quot;$fn Could not open WMI access to $computer&amp;quot;
		return 1
	}

	switch ( $type )
	{
		&amp;#39;reg_sz&amp;#39;
			{
				$r = $wmi.GetStringValue( $hive, $keyName, $valueName )
				$value.Value = $r.sValue
			}
		&amp;#39;reg_dword&amp;#39;
			{
				$r = $wmi.GetDWORDValue( $hive, $keyName, $valueName )
				$value.Value = $r.uValue
			}
		default
			{
				write-error &amp;quot;$fn Unsupported type: $type&amp;quot;
			}
	}

	$wmi = $null

	return $r.ReturnValue
}

function Get-ExchangeVersion
{
	Param(
		[string]$computer = &amp;#39;.&amp;#39;
	)

	[string]$fn = &amp;quot;Get-ExchangeVersion:&amp;quot; ## function name

	if( $computer -eq &amp;#39;.&amp;#39; -or [String]::IsNullOrEmpty( $computer ) )
	{
		$computer = $env:ComputerName
	}

	## Exchange E16 (assumption!)
	## HKLM\Software\Microsoft\ExchangeServer\v16\Setup
	## MsiProductMajor (DWORD 16)

	## Exchange 2013
	## HKLM\Software\Microsoft\ExchangeServer\v15\Setup
	## MsiProductMajor (DWORD 15)

	## Exchange 2010
	## HKLM\Software\Microsoft\ExchangeServer\v14\Setup
	## MsiProductMajor (DWORD 14)

	## Exchange 2007
	## HKLM\SOFTWARE\Microsoft\Exchange\Setup
	## MsiProductMajor (DWORD 8)

	## Exchange 2003
	## HKLM\SOFTWARE\Microsoft\Exchange\Setup
	## Services Version (DWORD 65)

	$v = 0

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\ExchangeServer\v16\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 16 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;E16&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\ExchangeServer\v15\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 15 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2013&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\ExchangeServer\v14\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 14 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2010&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\Exchange\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 8 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2007&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\Exchange\Setup&amp;#39; &amp;#39;Services Version&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 65 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2003&amp;#39;
		return $obj
	}

	### almost certainly not an Exchange server

	return $null
}
&lt;/pre&gt;&lt;/font&gt;
&lt;p&gt;Please follow me on Twitter, @essentialexch&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6990" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Registry/default.aspx">Registry</category></item><item><title>PowerShell Quick Script: Finding the Exchange Schema Version</title><link>http://theessentialexchange.com/blogs/michael/archive/2013/03/14/powershell-quick-script-finding-the-exchange-schema-version.aspx</link><pubDate>Thu, 14 Mar 2013 16:32:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6989</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Every major Exchange release comes with updates to the Active Directory schema. In this case, &amp;quot;major release&amp;quot; means new major version (at RTM), every service pack, and (probably) every Cumulative Update with the new servicing model introduced for Exchange 2013.&lt;/p&gt;
&lt;p&gt;Each update is unique to that particular release and, in general, they are cumulative. A notable exception to this was when Exchange Server 2007 SP3 had a higher schema version than that of Exchange Server 2010 RTM.&lt;/p&gt;
&lt;p&gt;Over the lifetime of modern Exchange (since the integration to Active Directory with Exchange 2000), there have been a number of issues making it important to know the current schema version of Exchange. Most instructions on the web suggest using ADSIEdit to examine the relevant variable and value.&lt;/p&gt;
&lt;p&gt;However, that is potentially risky (because ADSIEdit can be a dangerous tool) and can be a little confusing to use.&lt;/p&gt;
&lt;p&gt;Here is a quick little PowerShell script to report on the proper value:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font color="#339966" face="courier new,courier"&gt;$root&amp;nbsp; = [ADSI]&amp;quot;LDAP://RootDSE&amp;quot;&lt;br /&gt;$name&amp;nbsp; = &amp;quot;CN=ms-Exch-Schema-Version-Pt,&amp;quot; + $root.schemaNamingContext&lt;br /&gt;$value = [ADSI]( &amp;quot;LDAP://&amp;quot; + $name )&lt;br /&gt;&amp;quot;Exchange Schema Version = $( $value.rangeUpper )&amp;quot;&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The ms-Exch-Schema-Version-Pt attribute is never assigned to a class in the schema, it is used exclusively to identify the value of the Exchange Schema Version.&lt;/p&gt;
&lt;p&gt;To anyone who has used ADSI in PowerShell or VBScript before, the little four-line script will appear very familiar. The PowerShell ADSI accelerator syntax allows for the corresponding PowerShell script to be shorter than the equivalent VBScript script.&lt;/p&gt;
&lt;p&gt;In order for this script to work, it must be executed on a computer joined to an Active Directory domain. The execution context for the script (that is, the user account) requires no special privileges.&lt;/p&gt;
&lt;p&gt;Oh, and if you prefer PowerShell one-liners, here is the same script as a one-liner for you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font color="#339966" face="courier new,courier"&gt;&amp;quot;Exchange Schema Version = &amp;quot; + ([ADSI](&amp;quot;LDAP://CN=ms-Exch-Schema-Version-Pt,&amp;quot; + ([ADSI]&amp;quot;LDAP://RootDSE&amp;quot;).schemaNamingContext)).rangeUpper&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Follow me on Twitter @essentialexch&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6989" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>PowerShell Quick Script: Invoke-Splat</title><link>http://theessentialexchange.com/blogs/michael/archive/2013/03/08/powershell-quick-script-invoke-splat.aspx</link><pubDate>Sat, 09 Mar 2013 02:33:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6988</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;One of the features added to PowerShell v2 (and of course continued with PowerShell v3) is splatting. Without going into extreme detail (there are plenty of other blogs and books that do that), splatting allows you to pass parameters to a PowerShell cmdlet&amp;nbsp;via an associative array (that is a fancy name for a hash table).&lt;/p&gt;
&lt;p&gt;The hash table contains the parameter name as a &lt;em&gt;key&lt;/em&gt; and the parameter&amp;#39;s value as a &lt;em&gt;value&lt;/em&gt;. The hash table contains two matched arrays, the &lt;em&gt;keys &lt;/em&gt;array and the &lt;em&gt;values&lt;/em&gt; array. For example, if a hash table contains three values, then accessing $hash.keys[ 1 ] will provide the name of a given hash entry and $hash.values[ 1 ] will provide the corresponding value for that entry.&lt;/p&gt;
&lt;p&gt;The big deal behind splatting is that it allows you to easily construct custom parameter lists that get passed to cmdlets based on the special needs of a calling script. That sounds dense. :) But it is a good thing. For example, if you want to call a cmdlet with different parameters based on the time of day, or on a particular computer, or whether your script was started with a switch parameter, or whether a particular string parameter has a value - then splatting is for you.&lt;/p&gt;
&lt;p&gt;Splatting has a somewhat hidden advantage. Switch parameters require special handling under normal circumstances. With splatting, no special handling is required. You set a switch to $true or $false, without using special spacing and/or a colon.&lt;/p&gt;
&lt;p&gt;I use splatting extensively in scripts I have developed since PowerShell v2 was released. Today, my friend &lt;a title="The Accidental Citrix Admin" href="http://carlwebster.com/" target="_blank"&gt;Carl Webster, the Accidental Citrix Admin&lt;/a&gt;, asked about a way to simplify calling cmdlets in certain of his scripts. My answer was &amp;quot;use splatting&amp;quot;. After a couple of back-and-forth emails, about the best way to use splatting, I concluded that the easiest thing to do was to generalize a routine to provide splatting for a cmdlet and pass that over to Carl.&lt;/p&gt;
&lt;p&gt;After further thought and evolution on the basic concept of the routine I sent to Carl, I decided it could be a great deal more powerful (get it - powerful vs. PowerShell? HAHAHAHA) if I added in some logging capabilities and provided for the suppression of empty parameters. Thus was born Invoke-Splat. I have already taken Invoke-Splat and used it to simplify several of my own scripts. &lt;/p&gt;
&lt;p&gt;Invoke-Splat takes the cmdlet to invoke as a parameter, plus all of the parameters for that cmdlet, as &amp;quot;parameter value&amp;quot; pairs, plus a couple of optional switch parameters to control Invoke-Splat itself. Invoke-Splat is dependent on PowerShell&amp;#39;s named parameter matching, combined with positional parameter matching, and that everything that does not meet either of those, is stored in the special argument $args.&lt;/p&gt;
&lt;p&gt;The splatNotEmpty switch ensures that parameters whose values evaluate as either null or empty strings are not passed as parameters to the called cmdlet. This is especially important for parameters that cannot normally be null or empty!&lt;/p&gt;
&lt;p&gt;The splatDebug switch causes the cmdlet and each of the &amp;quot;parameter value&amp;quot; pairs to be output to the calling host.&lt;/p&gt;
&lt;p&gt;The splatCmdlet string parameter is (obviously) the name of the cmdlet that will be executed with the other parameters.&lt;/p&gt;
&lt;p&gt;Note that the other parameters may be empty!&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;Invoke-Splat Get-Process ID $PID FileVersionInfo $false -splatDebug&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;Invoke-Splat Get-Process ID $PID Name &amp;#39;&amp;#39; -splatDebug -splatNotEmpty&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Without further ado, here is Invoke-Splat.&lt;/p&gt;
&lt;p&gt;&lt;pre&gt;&lt;font color="green"&gt;function Invoke-Splat
{
	Param(
		[string]$splatCmdlet,
		[switch]$splatNotEmpty,
		[switch]$splatDebug
	)

	## $args[ 0 .. n ] are matching &amp;quot;parameter argument&amp;quot; pairs

	$splats = @{}
	for( $i = 0; $i –lt $args.Length; $i = $i + 2 )
	{
		if( $splatNotEmpty )
		{
			## don’t pass arguments with empty strings
			if( ( $args[ $i + 1 ] –as [string] ).Length –gt 0 )  
			{
				$splats.$( $args[ $i ] ) = $args[ $i + 1 ]
			}
		}
		else
		{
			$splats.$( $args[ $i ] ) = $args[ $i + 1 ]
		}
	}
	if( $splatDebug )
	{
		write-host -ForeGroundColor Yellow &amp;quot;DEBUG: Invoke-Splat: cmdlet = $splatCmdlet&amp;quot;
		foreach( $entry in $splats.Keys )
		{
			write-host -ForeGroundColor Yellow &amp;quot;DEBUG: Invoke-Splat: entry: $entry = $($splats.$entry)&amp;quot;
		}
	}

	&amp;amp; $splatCmdlet @splats
}&lt;/font&gt;&lt;/pre&gt;
&lt;p&gt;Please follow me on Twitter: @essentialexch.&lt;/p&gt;
&lt;p&gt;Thanks for your visit!&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6988" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Exchange Server 2013 Gotchas</title><link>http://theessentialexchange.com/blogs/michael/archive/2013/01/06/exchange-server-2013-gotchas.aspx</link><pubDate>Sun, 06 Jan 2013 18:15:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6987</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Exchange Server 2013 reached RTM a couple of months ago and has since reached General Availability (GA).&lt;/p&gt;&lt;p&gt;In my personal opinion, Exchange 2013 RTM is not ready for prime time. Microsoft made a decision to release all of Wave 15 (Office desktop applications and servers) at the same time; as well as release Windows 8, Windows RT, and Windows Server 2012 at the same time. I think this decision was seriously flawed. It is obvious that the products were not complete at RTM (witness Windows 2012 and Windows 8 having 300 MB of patches between RTM and GA, and Exchange 2013 not supporting any interop with prior versions of Exchange at either RTM or GA). It is easy to conclude that the RTM dates were artificially imposed.&lt;/p&gt;&lt;p&gt;I&amp;nbsp;have prepared a class on Exchange 2013 for one of my clients and part of that class was to discuss the limitations associated with Exchange 2013 RTM when compared to Exchange 2010 SP2. Note that the rest of the class discussed many of the new features and capabilities that have been added to Exchange 2013. So... the story is not all bad.&lt;/p&gt;&lt;p&gt;But as a summary of my opinion, Exchange 2013 RTM is not ready for prime time. Right now, it can only be installed in a green-field environment (that is, an environment where Exchange did not previously exist), so it is a safe bet that the Exchange team agrees with that as well. We can hope that some updates will quickly come out to address some of the current deficiencies.&lt;/p&gt;&lt;p&gt;This list is by no means exhaustive. And, as always, whether a particular issue is important to your organization requires you to evaluate your environment.&lt;/p&gt;&lt;p&gt;&lt;font size="4"&gt;OWA&lt;/font&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div&gt;Help -&amp;gt; About is gone&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;It&amp;#39;s very slow.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;No S/MIME support&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;No Public Folder support, either for legacy public folders or modern public folders.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;No distribution list moderation&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;No way to move the reading pane&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Built-in spell-check is gone. IE 10 provides spell-check natively, but earlier versions of IE do not. A third-party add-in or an alternate browser is required.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Other things are gone; don&amp;#39;t waste too much time looking for them.&amp;nbsp;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;font size="4"&gt;Client Connectivity&lt;/font&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div&gt;No BES support&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;...on a related note (and likely the primary reason BES is not yet available), the CDO/MAPI download is not yet available for Exchange 2013.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Outlook 2003 is no longer supported.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Direct MAPI access to the Exchange server is no longer supported.&amp;nbsp; RPC/HTTP (Outlook Anywhere) is required.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Outlook now reports that the server is it connected to is &amp;lt;&amp;lt;guid&amp;gt;&amp;gt;@&amp;lt;&amp;lt;active-directory-domain&amp;gt;&amp;gt;. This is intentional, if misguided.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;font size="4"&gt;Installation and Architecture&lt;/font&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div&gt;Cannot uninstall individual roles from a server, must uninstall all of Exchange&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Install is painfully slow&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The Hub Transport role is gone. There is now a Front End Transport service on CAS servers and Mailbox Transport services on Mailbox servers.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The Unified Messaging role is gone. There is a now a Unified Messaging Call Router service on CAS servers and a Unified Messaging service on Mailbox servers.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The CAS consists of three pieces: CAFE&amp;#39; (Client Access Front End), which proxies all end-user protocols to the appropriate mailbox server (completing the decoupling of the MAPI endpoint started in Exchange 2010) and handles Outlook Web App; FET (Front End Transport) which proxies SMTP protocols to the mailbox server and is responsible for TLS setup; and Unified Messaging Call Router.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;After an installation or an upgrade, services not starting is an endemic problem. You will likely need to increase ServicesPipeTimeout on your Exchange servers.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Documentation is minimal at best&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Deployment and sizing guidance is non-existent.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Cannot be installed along with Exchange 2007 or Exchange 2010&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Exchange 2013 Edge server is not available&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Forefront Protection for Exchange is gone&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;For both Exchange 2010 and Exchange 2013, applying updates can often screw up the winrm configuration. If you get errors in EMS or EAC regarding &amp;quot;The WS-Management service cannot process the request&amp;quot;, try this first:&lt;br /&gt;&lt;br /&gt;&amp;nbsp; winrm quickconfig&lt;br /&gt;&amp;nbsp;&amp;nbsp;iisreset&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Since you cannot interop with legacy public folders in RTM, if you need an Organizational Forms Library, you must create it yourself. To create an&amp;nbsp;Organizational Forms Library:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1. Create &amp;quot;Organizational Forms Library&amp;quot; folder under the Eforms Registry:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;New-publicfolder &amp;quot;Organizational Forms Library&amp;quot; -path &amp;quot;\non_ipm_subtree\Eforms Registry&amp;quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;2. Set the locale ID for the Org Forms Library:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;Set-PublicFolder &amp;quot;\non_ipm_subtree\Eforms Registry\Organizational Forms Library&amp;quot; -EformsLocaleID EN-US&lt;br /&gt;&lt;br /&gt;It is no longer necessary to set the PR_URL_NAME property.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;font size="4"&gt;Exchange Management&lt;/font&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div&gt;The Exchange Management Console is gone as is the Exchange Control Panel. They are mainly replaced by the Exchange Administration Center (EAC); which is completely web based.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;If you are attempting to use EAC with IE 10, you need KB2761465 (released on December 11, 2012).&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The Exchange Best Practices analyzer is no more.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The Exchange Mail Flow Troubleshooter is no more.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The Exchange Performance Troubleshooter is no more.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The Exchange Routing Log Viewer is no more.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The EAC does not provide a preview (or an after-view for that matter) of the PowerShell it executed.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Antispam and antimalware is crippled compared to earlier releases&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;The E15 AV does not offer a quarantine&lt;br /&gt;&amp;nbsp;&amp;nbsp;The E15 AS does offer a quarantine (for the administrator, not per-user)&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Antispam cannot be managed from the Exchange Administration Center; it must be managed using PowerShell in the Exchange Management Shell&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Kerberos Constrained Delegation (KCD) is not supported for OWA&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;This isn&amp;#39;t new, but should be reinforced: DO NOT TURN OFF IPV6. Microsoft does not perform any testing to determine the effects of disabling IPv6. Therefore, Microsoft recommends that you leave IPv6 enabled, even if you do not have an IPv6-enabled network, either native or tunneled. See &lt;a href="http://technet.microsoft.com/en-us/network/cc987595.aspx"&gt;http://technet.microsoft.com/en-us/network/cc987595.aspx&lt;/a&gt;.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;System Center Data Protection Manager (DPM) version required for backups of Exchange 2013 is SC DPM 2012 SP1&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;font size="4"&gt;Mailboxes and Databases&lt;/font&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;div&gt;Mailbox sizes MAY appear to increase substantially when moving a mailbox to an Exchange 2013 mailbox server. In Exchange 2010 and before, only select properties of a particular mailbox item were assigned as part of the mailboxes diskspace allocation, causing under-reporting. Now, all item properties for a particular mailbox item are assigned to the mailboxes disk space allocation. However, some items in Exchange 2013 are now compressed which were not before. This can lead to a reduction in reported and allocated diskspace. So, prediction is basically impossible. Just be aware that it may happen.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Corrupt PropertyTags during a mailbox move are common. Using (Get-MoveRequestStatistics -IncludeReport &amp;lt;&amp;lt;alias-name&amp;gt;&amp;gt;).Report.Failures you can find the rule or message that is causing the problem and remove it.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Changes made to improve Office 365 and hybrid deployments had an unintended consequence (this is my conclusion). When you are performing impersonation (e.g., to open a different user&amp;#39;s mailbox via EWS), you should always impersonate using the email address.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;As a corollary, it is recommended that the account UPN match the primary email address.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;In a change that you won&amp;#39;t know about until you need to know it - MRS Proxy is not enabled by default in Exchange 2013. Use Set-WebServicesVirtualDirectory to enable it.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Clean-MailboxDatabase is gone&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;Update-StoreMailboxState is designed to replace it&lt;br /&gt;&amp;nbsp;&amp;nbsp;Requires that you know the guid of the deleted mailbox&lt;br /&gt;&amp;nbsp;&amp;nbsp;No on-premises cmdlets allow you to find those out!&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Get-LogonStatistics is non-operational. The cmdlet is still present, but it doesn&amp;#39;t work.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Exchange 2013 Enterprise Edition supports only 50 mailbox databases instead of the 100 supported in Exchange 2010&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;MRM 1.0 (Messaging Record Management - Managed Folders) is non-operational on Exchange 2013 mailbox servers. The cmdlets are still present, and will affect down-level servers (which you can&amp;#39;t use right now), but they don&amp;#39;t work with Exchange 2013 servers.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;Moving mailboxes using the migration wizard in EAC can generate large amounts of log files for the database which hosts the arbitration mailbox. Use New-MoveRequest instead.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;In a positive change, Office Filter Packs are no longer required. There is a new search technology used in all Wave 15 (Office 2013) products and it knows how to search all the Office file formats. This also includes the PDF format, so a separate iFilter installation for PDF is no longer required.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;When using Database Availability Groups (DAGs) on Windows Server 2012, you must manually create the Cluster Network Object (CNO) and provision the CNO by assigning permissions to it.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;While Windows Server 2012 provides support for large sectors (4 KB), Exchange 2013 does not support large sectors. Emulation of large sectors (512 E) is supported provided that all database copies are on 512 E.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;&lt;div&gt;The above statement is, in general, true. Additional capabilities of Windows Server 2012 are not supported by Exchange Server 2013. This specifically includes but is not limited to Hyper-V Replica.&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Good luck!&lt;/p&gt;&lt;p&gt;[Edit at 19:55 on 6-January-2013 to clarify why you may need an organizational forms library and to add the note regarding lack of spell-check in OWA (hat-tips to Ben Winzenz and Tim Robichaux).]&lt;/p&gt;&lt;p&gt;[Edit at 21:24 on 8-January-2013 to fix several grammar and spelling errors. Oops.]&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6987" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Upgrade_2F00_Migration/default.aspx">Upgrade/Migration</category></item><item><title>Windows Management Framework 3.0 / PowerShell 3.0 and Exchange</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/12/13/windows-management-framework-3-0-powershell-3-0-and-exchange.aspx</link><pubDate>Thu, 13 Dec 2012 14:31:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6986</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;In the last few days, Windows Management Framework 3.0 (WMF 3.0) has begun appearing in Microsoft Update (MU), Windows Update (WU), Windows Software Update Services (WSUS), and on Configuration Manager Software Update Points. This basically means that Microsoft is now suggesting that WMF 3.0 be installed on all of your servers where the update is applicable.&lt;/p&gt;&lt;p&gt;This update is released as &lt;a title="KB 2506146" href="http://support.microsoft.com/kb/2506146" target="_blank"&gt;KB 2506146&lt;/a&gt; and &lt;a title="KB 2506143" href="http://support.microsoft.com/kb/2506143" target="_blank"&gt;KB 2506143&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;DON&amp;#39;T DO IT.&lt;/p&gt;&lt;p&gt;WMF 3.0 includes PowerShell 3.0.&lt;/p&gt;&lt;p&gt;PowerShell 3.0 is a great improvement to PowerShell. No question about it.&lt;/p&gt;&lt;p&gt;However, Exchange 2010 is NOT currently qualified to work with PowerShell 3.0. And, in fact, it doesn&amp;#39;t. It will break. PowerShell 3.0 compatibility will come with Exchange 2010 Service Pack 3, due sometime in the first half of calendar year 2013 (word on the street says first quarter).&lt;/p&gt;&lt;p&gt;If you have installed WMF 3.0, you will also find that Exchange Update Rollups will fail to install.&lt;/p&gt;&lt;p&gt;Exchange 2007 is also not qualified to work with PowerShell 3.0. And, as far as I know, never will be.&lt;/p&gt;&lt;p&gt;You absolutely, positively, do not want to install the update on your Exchange servers.&lt;/p&gt;&lt;p&gt;You also do not want to install the update on workstations or utility servers where you have Exchange Management Tools installed.&lt;/p&gt;&lt;p&gt;I have also heard reports that SharePoint 2010 also has problems with the WMF 3.0 release. I can believe it. You should avoid that as well.&lt;/p&gt;&lt;p&gt;Good luck!&lt;/p&gt;&lt;p&gt;P.S. Exchange 2013 does work with WMF 3.0 and in fact, WMF 3.0 is required to install Exchange 2013. If you are one of the rare few running Exchange 2013, you do not need to be concerned about this.&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6986" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Upgrade_2F00_Migration/default.aspx">Upgrade/Migration</category></item><item><title>Wave 15 reaches RTM, including Exchange 2013</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/10/11/wave-15-reaches-rtm-including-exchange-2013.aspx</link><pubDate>Thu, 11 Oct 2012 22:23:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6985</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Wave 15 of Office products reached the Release To Manufacturing (RTM) stage today, October 11, 2012 (10/11/12 - heh).&lt;/p&gt;&lt;p&gt;These products include Exchange Server 2013, Lync Server 2013, SharePoint Server 2013, and Microsoft Office 2013.&lt;/p&gt;&lt;p&gt;Note that RTM, also known as &amp;quot;code-complete&amp;quot;, means that development is done on the products and they will now be written to ISOs, DVDs burned, retail boxes built, etc.&lt;/p&gt;&lt;p&gt;This does NOT mean that the releases are currently available. As of today, releases are still only available to TAP and RDP participants.&lt;/p&gt;&lt;p&gt;General Availability (GA) defines the timeframe when the releases will be available to everyone. This is targeted for first quarter calendar-year 2013 for all the Wave 15 products.&lt;/p&gt;&lt;p&gt;However, volume license customers will be able to download these products on the Volume Licensing Service Center (VLSC) by mid-November and the products will be on the VL price lists starting December 1, 2012.&lt;/p&gt;&lt;p&gt;Specifically for Exchange, the build number for the RTM release is 15.0.516.32. Exchange 2013 was code-named E15 throughout its development cycle.&lt;/p&gt;&lt;p&gt;For your information, &amp;quot;Wave 15&amp;quot; refers to the fact that version numbers of all Office product lines have been synchronized. The major version number for all products is &amp;quot;15&amp;quot;.&lt;/p&gt;&lt;p&gt;For more information about Exchange, refer to the Exchangae team blog: &lt;a title="The New Exchange Reaches RTM!" href="http://blogs.technet.com/b/exchange/archive/2012/10/11/the-new-exchange-reaches-rtm.aspx" target="_blank"&gt;The New Exchange Reaches RTM!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;For more information about the other Office products, refer to the Office blog: &lt;a title="Office Reaches RTM" href="http://blogs.office.com/b/office-news/archive/2012/10/11/office-reaches-rtm.aspx" target="_blank"&gt;Office Reaches RTM.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It is interesting to note that both releases refer to &amp;quot;the New Exchange&amp;quot; and &amp;quot;the new Office&amp;quot; - taking a cue from Apple, I presume; in not tying the announcement to a specific release of the product.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6985" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Upgrade_2F00_Migration/default.aspx">Upgrade/Migration</category></item><item><title>Security Advisory Affecting Exchange: 2749655</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/10/09/security-advisory-affecting-exchange-2749655.aspx</link><pubDate>Tue, 09 Oct 2012 23:23:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6984</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Today Microsoft released &lt;a title="SA 2749655: Compatibility Issues Affecting Signed Microsoft Binaries" href="http://technet.microsoft.com/en-us/security/advisory/2749655" target="_blank"&gt;Security Advisory 2749655, Compatibility Issues Affecting Signed Microsoft Binaries&lt;/a&gt;. I encourage you to read the security advisory. We also saw that Exchange team post the following blog entry &lt;a title="Re-released Exchange 2010 and Exchange 2007 update rollups" href="http://blogs.technet.com/b/exchange/archive/2012/10/09/re-released-exchange-2010-and-exchange-2007-update-rollups.aspx" target="_blank"&gt;Re-released Exchange 2010 and Exchange 2007 update rollups&lt;/a&gt;. Microsoft re-released the rollups in order to ensure that third party programs are not impacted by the security advisory.&lt;/p&gt;&lt;p&gt;That is, Microsoft re-released the updates as a pre-emptive measure - just in case.&lt;/p&gt;&lt;p&gt;If you have installed the security advisory, you really don&amp;#39;t need to re-install the updates. Quoting from the security advisory:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What does this update do?&lt;/strong&gt; &lt;br /&gt;This update will help to ensure the continued functionality of all software that was signed with a specific certificate that did not use a timestamp Enhanced Key Usage (EKU) extension. To extend their functionality, WinVerifyTrust will ignore the lack of a timestamp EKU for these specific X.509 signatures.&lt;br /&gt;......&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note regarding the impact of not installing a rereleased update&lt;/strong&gt; &lt;br /&gt;Customers who installed the original updates are protected from the vulnerabilities addressed by the updates. However, because improperly signed files, such as executable images, would not be considered correctly signed after the expiration of the CodeSign certificate used in the signing process of the original updates, Microsoft Update may not install some security updates after the expiration date. Other effects include, for example, that an application installer may display an error message. Third-party application whitelisting solutions may also be impacted. Installing the rereleased updates remediates the issue for the affected updates.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Long story short - Microsoft has released the updates &amp;quot;just in case&amp;quot;. &lt;/p&gt;&lt;p&gt;Given that Exchange 2010 SP2 Update Rollups can take upwards of an hour to install and your Exchange server is offline while you doing the installation&amp;nbsp;- you may just want to wait for the next UR.&lt;/p&gt;&lt;p&gt;Please note: the Exchange 2010 SP2 UR4 re-release does include one new patch. You can install that separately in a very short timeframe: &lt;a title="Outlook only returns one result after you click the &amp;quot;Your search returned a large number of results. Narrow your search, or click here to view all results&amp;quot; message" href="http://support.microsoft.com/kb/2756987" target="_blank"&gt;Outlook only returns one result after you click the &amp;quot;Your search returned a large number of results. Narrow your search, or click here to view all results&amp;quot; message.&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6984" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category></item><item><title>Microsoft Security Advisory 2737111 - Exchange 2007/2010/2013</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/07/24/microsoft-security-advisory-2737111-exchange-2007-2010-2013.aspx</link><pubDate>Wed, 25 Jul 2012 01:07:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6983</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Well, in case you haven’t seen it, an Exchange Security advisory was released today.&lt;/p&gt;&lt;p&gt;“Vulnerabilities in Microsoft Exchange and FAST Search Server 2010 for SharePoint Parsing Could Allow Remote Code Execution”&lt;/p&gt;&lt;p&gt;&lt;a href="http://technet.microsoft.com/en-us/security/advisory/2737111"&gt;http://technet.microsoft.com/en-us/security/advisory/2737111&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And yes, it also affects the Exchange 2013 Preview.&lt;/p&gt;&lt;p&gt;It wasn’t immediately obvious to me, but every Exchange CAS/CAFE has these libraries installed. Microsoft licenses them from Oracle.&lt;/p&gt;&lt;p&gt;I’m guessing that that will change the workaround to:&lt;/p&gt;&lt;p&gt;&lt;font color="green" face="courier new,courier"&gt;Get-OwaVirtualDirectory |? {&lt;br /&gt;$_.OwaVersion -eq &amp;#39;Exchange2007&amp;#39; -or $_.OwaVersion -eq &amp;#39;Exchange2010&amp;#39; –or $_.OwaVersion –eq &amp;#39;Exchange2013&amp;#39; } | &lt;br /&gt;Set-OwaVirtualDirectory -WebReadyDocumentViewingOnPublicComputersEnabled:$False `&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; -WebReadyDocumentViewingOnPrivateComputersEnabled:$False&lt;/font&gt;&lt;/p&gt;&lt;p&gt;But I’m on the road and don’t have access to my E15 test lab at the moment…&lt;/p&gt;&lt;p&gt;Thanks to Susan Bradley, the SBS Diva, for pointing out to me that this doesn&amp;#39;t require third party add-ins.&lt;/p&gt;&lt;p&gt;[Edit]&lt;/p&gt;&lt;p&gt;More information:&lt;/p&gt;&lt;p&gt;&amp;quot;In Microsoft Exchange Server 2007 and Exchange Server 2010, Outlook Web App (OWA) users are provided with a feature called WebReady Document Viewing that allows users to view certain attachments as a web page instead of relying on local applications to open/view it. Oracle Outside In is used by the conversion process in the server backend to support the WebReady feature. Microsoft licenses this library from Oracle.&amp;quot;&lt;/p&gt;&lt;p&gt;In the Exchange Server 2007/2010 scenario, the conversion process that uses Oracle Outside In, TranscodingService.exe, runs as LocalService.&lt;/p&gt;&lt;p&gt;&lt;a href="http://blogs.technet.com/b/srd/archive/2012/07/24/more-information-on-security-advisory-2737111.aspx"&gt;http://blogs.technet.com/b/srd/archive/2012/07/24/more-information-on-security-advisory-2737111.aspx&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6983" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Exchange Server 2013 on Windows Server 2012</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/07/18/exchange-server-2013-on-windows-server-2012.aspx</link><pubDate>Thu, 19 Jul 2012 00:43:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6982</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Unless you&amp;#39;ve been living under a rock, you know that the public betas of all the Wave 15 products, including Office, Exchange, Lync, SharePoint, etc. were released earlier this week as &amp;quot;2013&amp;quot; products. These were all released on Monday July 16, 2012. This follows by a&amp;nbsp;couple of weeks&amp;nbsp;the &amp;quot;release previews&amp;quot; of Windows 8 and Windows Server 2012.&lt;/p&gt;&lt;p&gt;You can download the Exchange Server 2013 public beta/preview &lt;a title="Download Exchange Server 2013 public beta" href="http://technet.microsoft.com/en-US/evalcenter/hh973395" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In the original release, it was not supported to install the Exchange preview on the Server 2012 preview. Today, Microsoft has changed that guidance and now supports installing Exchange Server 2013 on Windows Server 2012.&lt;/p&gt;&lt;p&gt;At this time, it requires a couple of additional manual steps. You can find information about how to install Exchange Server 2013 on Windows Server 2012 &lt;a title="Install Exchange Server 2013 on Windows Server 2012" href="http://technet.microsoft.com/en-us/library/e21cf744-7813-48b3-9293-5cecd89a6c25(EXCHG.150)#WS2012CAS" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;We can reasonably expect that this will be cleaned up before RTM.&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6982" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Windows/default.aspx">Windows</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Upgrade_2F00_Migration/default.aspx">Upgrade/Migration</category></item><item><title>Bit Shifting in PowerShell, Redux</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/05/10/bit-shifting-in-powershell-redux.aspx</link><pubDate>Thu, 10 May 2012 16:27:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6981</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;I do bit shifting in PowerShell all the time. It&amp;#39;s quite necessary when you are working with option values in Active Directory, WMI, and various&amp;nbsp;Win32 interfaces. .NET tends to use enumerations, which are tad easier to deal with in PowerShell.&lt;/p&gt;&lt;p&gt;I had never felt it necessary to break my routines out before, but I saw a posting on this topic by another individual today and I just thought he was making it harder than it had to be. I wanted to present another option to the community for these types of routines. I&amp;#39;m not putting down his work in any way - it was quite ingenious. I never would&amp;#39;ve thought of doing it that way.&lt;/p&gt;&lt;p&gt;Just FYI, &amp;#39;shr&amp;#39; is an abbreviation for &amp;#39;shift right&amp;#39; and correspondingly &amp;#39;shl&amp;#39; is an abbreviation for &amp;#39;shift left&amp;#39;. When I was in college (in the stone age) those function names were used in &amp;quot;Pascal extension libraries&amp;quot; and in &amp;quot;Fortran libraries&amp;quot; for performing shift operations. Shift operations are built-in as native operations to many (most?) compiled languages such as C and its various dialects.&lt;/p&gt;&lt;p&gt;Enjoy.&lt;/p&gt;
&lt;p&gt;
&lt;font color="green"&gt;
&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;###
### Bit-shift operations
### bit-shift.ps1
###
### for bytes and short integers
###
### Michael B. Smith
### michael at TheEssentialExchange.com
### May, 2012
###

$bitsPerByte = 8	## [byte]
$bitsperWord = 16	## [int16] or [uint16]

function shift-left( [int]$valuesize, [int]$mask, $val, [int]$bits )
{
	if( $bits -ge $valuesize )
	{
		return 0
	}
	if( $bits -eq 0 )
	{
		return $val
	}
	if( $bits -lt 0 )
	{
		write-error &amp;quot;Can&amp;#39;t shift by a negative value of bits&amp;quot;
		return -1
	}

	### it&amp;#39;s possible to write this so that you never
	### overshift and generate an overflow. it&amp;#39;s easier
	### to use a larger variable and mask at the end.

	[int]$result = $val
	for( $i = 0; $i -lt $bits; $i++ )
	{
		$result *= 2
	}

	return ( $result -band $mask )
}

function shift-right( [int]$valuesize, [int]$mask, $val, [int]$bits )
{
	if( $bits -ge $valuesize )
	{
		return 0
	}
	if( $bits -eq 0 )
	{
		return $val
	}
	if( $bits -lt 0 )
	{
		write-error &amp;quot;Can&amp;#39;t shift by a negative value of bits&amp;quot;
		return -1
	}

	for( $i = 0; $i -lt $bits; $i++ )
	{
		## normally PowerShell does banker&amp;#39;s rounding (well, .NET does)
		## we have to override that here to get true integer division.
		$val = [Math]::Floor( $val / 2 )
	}

	return $val
}

function shl-byte( [byte]$val, [int]$bits )
{
	$result = shift-left $bitsPerByte 0xff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [byte]$result )
}

function shr-byte( [byte]$val, [int]$bits )
{
	$result = shift-right $bitsPerByte 0xff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [byte]$result )
}

function shl-word( [uint16]$val, [int]$bits )
{
	$result = shift-left $bitsPerWord 0xffff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [uint16]$result )
}

function shr-word( [uint16]$val, [int]$bits )
{
	$result = shift-right $bitsPerWord 0xffff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [uint16]$result )
}

function shl( $val, [int]$bits )
{
	if( $val -is [byte] )
	{
		return ( shl-byte $val $bits )
	}
	elseif( $val -is [int16] )
	{
		return [int16]( shl-word $val $bits )
	}
	elseif( $val -is [uint16] )
	{
		return ( shl-word $val $bits )
	}
	elseif( ( $val -lt 65536 ) -and ( $val -ge 0 ) ) ### pretend it&amp;#39;s uint16
	{
		return ( shl-word ( [uint16]$val ) $bits )
	}

	write-error &amp;quot;value is an invalid type&amp;quot;
	return -1
}

function shr( $val, [int]$bits )
{
	if( $val -is [byte] )
	{
		return ( shr-byte $val $bits )
	}
	elseif( $val -is [int16] )
	{
		return [int16]( shr-word $val $bits )
	}
	elseif( $val -is [uint16] )
	{
		return ( shr-word $val $bits )
	}
	elseif( ( $val -lt 65536 ) -and ( $val -ge 0 ) ) ### pretend it&amp;#39;s uint16
	{
		return ( shr-word ( [uint16]$val ) $bits )
	}

	write-error &amp;quot;value is an invalid type&amp;quot;
	return -1
}
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6981" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>DPM 2010 Backing Up SQL 2012</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/05/09/dpm-2010-backing-up-sql-2012.aspx</link><pubDate>Wed, 09 May 2012 23:32:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6980</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;I can&amp;#39;t speak as to whether this is supported or not, but if you want DPM 2010 to be able to back up a SQL 2012 database instance, then you need to make a simple security change. Otherwise, DPM 2010 reports that &amp;quot;The replica is not consistent.&amp;quot;&lt;/p&gt;&lt;p&gt;In words, NT AUTHORITY\System must be granted Sysadmin permissions on the database instance. NT AUTHORITY\System, by default, has public permissions on the instance in SQL 2012. However, in earlier versions of SQL, NT AUTHORITY\System has Sysadmin permissions at installation.&lt;/p&gt;&lt;p&gt;Below, the first figure identifies the specific login in the SQL Server Management Studio that has to be modified. The second figure identifies the box that needs to be checked. After checking the box, click OK to commit the change.&lt;/p&gt;&lt;p&gt;After this, you can open the DPM Console and for all of those SQL 2012 databases that are not consistent, go to the Protection workspace, right-click on the database(s), and then click on &amp;quot;Perform Consistency Check&amp;quot;.&lt;/p&gt;&lt;p&gt;Figure 1 - The NT AUTHORITY\System login&lt;/p&gt;&lt;p&gt;&lt;a href="http://theessentialexchange.com/blogs/michael/DPM2010-SQL2012-Figure1.png"&gt;&lt;img border="0" src="http://theessentialexchange.com/blogs/michael/DPM2010-SQL2012-Figure1.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Figure 2 - Setting the Sysadmin Server Instance Role&lt;/p&gt;&lt;p&gt;&lt;a href="http://theessentialexchange.com/blogs/michael/DPM2010-SQL2012-Figure2.png"&gt;&lt;img border="0" src="http://theessentialexchange.com/blogs/michael/DPM2010-SQL2012-Figure2.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6980" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/SQL/default.aspx">SQL</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/DPM/default.aspx">DPM</category></item><item><title>Processing Large and Embedded Groups in PowerShell</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/05/04/processing-large-and-embedded-groups-in-powershell.aspx</link><pubDate>Fri, 04 May 2012 17:29:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6979</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;I&amp;#39;m working with a client that has&amp;nbsp;- over an extended period of time - accumulated thousands of security groups, most of which are mail enabled for use by both Exchange and for setting rights on various objects.&lt;/p&gt;&lt;p&gt;Becasue they use embedded groups (that is, placing a group as a member of another group), they have a horrible time keeping straight who is actually a member of those groups.&lt;/p&gt;&lt;p&gt;Recently an unintentional disclosure at the client has caused them to desire to clean this mess up. :-)&lt;/p&gt;&lt;p&gt;They found that many tools weren&amp;#39;t robust enough to deal with their environment. Their groups may contain thousands of members and dozens of embedded groups. And sometimes, the groups are recursive (or nested). That is, GroupA includes GroupB, GroupB includes GroupC, and GroupC includes GroupA. Also, due to historial reasons, many of their users have non-standard primary group memberships and those needed to also be included as part of the evaluation process.&lt;/p&gt;&lt;p&gt;Note: be aware - most tools will only return 1,500 members for a group (1,000 if your FFL is Windows 2000 mixed or Windows 2000 native). This includes most tools from Microsoft (e.g., dsquery and dsgroup). Some of the tools that handle that properly will go into an infinite loop if there are nested groups. Since the primary group is stored differently than other group memberships, most tools simply ignore it (the RID of the group is stored in the primaryGroupId attribute of a user object, instead of using a memberOf backlink or the member attribute forward link from the group itself).&lt;/p&gt;&lt;p&gt;We were unable to find a tool (which doesn&amp;#39;t mean one isn&amp;#39;t out there!) that handled all of these issues properly.&lt;/p&gt;&lt;p&gt;So, I wrote one. In PowerShell.&lt;/p&gt;&lt;p&gt;Note that performance is not great when you are scanning nested groups. This is because it is necessary to evaluate every member to determine the type of the member - user, group, contact, etc. That adds significant additional processing overhead.&lt;/p&gt;&lt;p&gt;Each individual piece of this script is pretty obvious (except for the &amp;quot;range&amp;quot; processing required for looking at large group memberships). But after putting it all together, it&amp;#39;s a thing of magic. :-)&lt;/p&gt;&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;p&gt;
&lt;font color="green"&gt;
&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;###
### Get-GroupMember
###
### This function processes LARGE groups. Most normal utilities are limited
### to returning a maximum of 1,500 members for a group. To get all members
### of a group requires using a &amp;quot;ranged&amp;quot; member attribute. Few programs,
### including many from Microsoft, go to that much trouble. This one does.
###
### Also, retrieving membership from embedded groups, while avoiding the
### problems that can occur with group recursion, is something that many
### programs do not handle properly. This one does.
###
### Also, some programs do not handle empty groups properly (including the 
### example range program on MSDN from Microsoft). This one does.
###
### Also, some programs do not also check for the primaryGroupID membership, 
### and thus cannot return the membership of, for example, &amp;#39;Domain Users&amp;#39;. 
### This one does.
###
### The ADSpath for each member of the group is written to the pipeline.
###
### Michael B. Smith
### michael at TheEssentialExchange dot come
### May, 2012
###
### Parameters:
###
###	-group 			The short name for the group. This is looked
###				up to find the distinguishedName of the group.
###
###	-ExpandEmbeddedGroups	Whether to recurse and get the membership of
###				groups contained within the parent group. If
###				this option is specified, all embedded groups
###				are scanned (including groups embedded within
###				groups embedded within groups,etc. etc.).
###
###	-Verbose		Display to the host function entry/exit and
###				status information.
###
###	-VeryVerbose		Display to the host the ADSpath of each member
###				of the group (as well as write it to the pipe).
###
###	-Statistics		Display to the host some basic statistics about
###				the query (number of users, number of embedded
###				groups, number of contacts).
###

Param(
	[string]$group	= (throw &amp;quot;group must be specified&amp;quot;),
	[switch]$ExpandEmbeddedGroups,
	[switch]$Statistics,
	[switch]$Verbose,
	[switch]$VeryVerbose
)

### for the Statistics option

$script:groupUsers    = 0
$script:groupGroups   = 0
$script:groupContacts = 0

function msg
{
	if( -not $Verbose )
	{
		return
	}

	$str = &amp;#39;&amp;#39;
	foreach( $arg in $args )
	{
		$str += $arg
	}
	write-host $str
}

function vmsg
{
	if( -not $VeryVerbose )
	{
		return
	}
	msg $args
}

function Get-PrimaryGroupID
{
	Param(
		[string]$indent,
		[string]$ADSpath
	)

	msg &amp;quot;${indent}Get-PrimaryGroupId: enter, ADSpath = $adspath&amp;quot;

	[string]$pgToken = &amp;#39;primaryGroupToken&amp;#39;

	### format of argument: LDAP://CN=Domain Users,CN=Users,DC=smithcons,DC=local

	$groupDE  = New-Object System.DirectoryServices.DirectoryEntry( $ADSpath )
	$searcher = New-Object System.DirectoryServices.DirectorySearcher( $groupDE )
	$searcher.Filter = &amp;quot;(objectClass=*)&amp;quot;

	$searcher.PropertiesToLoad.Add( $pgToken ) | Out-Null

	$result = $searcher.FindOne()
	if( $result -ne $null )
	{
		if( $result.Properties.Contains( $pgToken ) -eq $true )
		{
			msg &amp;quot;${indent}Get-PrimaryGroupId: exit, token = $($result.Properties.primarygrouptoken)&amp;quot;

			return $result.Properties.primarygrouptoken
		}
	}

	msg &amp;quot;${indent}Get-PrimaryGroupId: exit, token not found&amp;quot;
	return 0
}

function Search-PrimaryGroupID
{
	Param(
		[string]$indent,
		[string]$namingContext,
		[int]$primaryGroup,
		[hashtable]$dictionary
	)

	msg &amp;quot;${indent}Search-PrimaryGroupId: enter, namingcontext = &amp;#39;$namingContext&amp;#39;, primaryGroup = $primaryGroup&amp;quot;

	$ldapFilter = &amp;quot;(primaryGroupID=$primaryGroup)&amp;quot;

	$directorySearcher = New-Object System.DirectoryServices.DirectorySearcher
	$directorySearcher.PageSize    = 1000
	$directorySearcher.SearchRoot  = ( &amp;quot;LDAP://&amp;quot; + $namingContext )
	$directorySearcher.SearchScope = &amp;quot;subtree&amp;quot;
	$directorySearcher.Filter      = $ldapFilter

	### load the properties we want

	$directorySearcher.PropertiesToLoad.Add( &amp;quot;distinguishedName&amp;quot; ) | Out-Null
	$directorySearcher.PropertiesToLoad.Add( &amp;quot;objectClass&amp;quot; )       | Out-Null

	$results = $directorySearcher.FindAll()
	if( $results -ne $null )
	{
		msg &amp;quot;${indent}Search-PrimaryGroupId: found $($results.Count) results&amp;quot;
		foreach( $result in $results )
		{
			$myadspath   = $result.Path
			$objCount    = $result.Properties.objectclass.count
			$objectClass = $result.Properties.objectclass[ $objCount - 1 ]

			if( $objectClass -eq &amp;#39;user&amp;#39; )
			{
				if( $dictionary.$myadspath -eq 1 )
				{
					msg &amp;quot;${indent}Search-PrimaryGroupID: continue duplicate user&amp;quot;
					return
				}
				$dictionary.$myadspath = 1
				$script:groupUsers++
				write-output $myadspath
				vmsg &amp;quot;${indent}Search-PrimaryGroupId: $myadspath&amp;quot;
			}
			else
			{
				write-error &amp;quot;Invalid objectclass for primarygroupid: $objectClass&amp;quot;
			}
		}
	}
	else
	{
		msg &amp;quot;${indent}Search-PrimaryGroupID: result from FindAll() was null&amp;quot;
	}

	msg &amp;quot;${indent}Search-PrimaryGroupId: exit&amp;quot;
}

function Search-Group
{
	Param(
		[string]$indent,
		[string]$ADSpath,
		[hashtable]$dictionary
	)

	### based originally on http://msdn.microsoft.com/en-us/library/bb885125.aspx
	### but has bug-fixes and enhancements

	msg &amp;quot;${indent}Search-Group: enter, $ADSpath&amp;quot;

	$groupDE  = New-Object System.DirectoryServices.DirectoryEntry( $ADSpath )
	$searcher = New-Object System.DirectoryServices.DirectorySearcher( $groupDE )
	$searcher.Filter = &amp;quot;(objectClass=*)&amp;quot;

	[bool]$lastLoop = $false
	[bool]$quitLoop = $false

	[int]$step = 999
	[int]$low  = 0
	[int]$high = $step

	do {
		if( $lastLoop -eq $false )
		{
			[string]$member = &amp;#39;member;range=&amp;#39; + $low.ToString() + &amp;#39;-&amp;#39; + $high.ToString()
		}
		else
		{
			[string]$member = &amp;#39;member;range=&amp;#39; + $low.ToString() + &amp;#39;-&amp;#39; + &amp;#39;*&amp;#39;
		}
		msg &amp;quot;${indent}Search-Group: member = $member&amp;quot;

		$searcher.PropertiesToLoad.Clear()        | Out-Null
		$searcher.PropertiesToLoad.Add( $member ) | Out-Null

		$result = $searcher.FindOne()
		if( $result -eq $null )
		{
			### not sure what to do here
			msg &amp;quot;${indent}Search-Group: searcher failure&amp;quot;
			break
		}

		if( $result.Properties.Contains( $member ) -eq $true )
		{
			$entries = $result.Properties.$member
			msg &amp;quot;${indent}Search-Group: entries.Count = $($entries.Count)&amp;quot;
			foreach( $entry in $entries )
			{
				if( $ExpandEmbeddedGroups )
				{
					$memberObj   = [ADSI] &amp;quot;LDAP://$entry&amp;quot;
					$objectClass = $memberObj.objectClass.Item( $memberObj.objectClass.Count - 1 )
					$myadspath   = $memberObj.Path
					$memberObj   = $null
				}
				else
				{
					$myadspath   = $entry
					$objectClass = &amp;#39;user&amp;#39;
				}
				write-output $myadspath ### output to pipeline

				switch( $objectClass )
				{
					&amp;#39;group&amp;#39;
						{
							if( $dictionary.$myadspath -eq 1 )
							{
								msg &amp;quot;${indent}Search-Group: continue duplicate group&amp;quot;
								continue
							}
							$dictionary.$myadspath = 1
							$script:groupGroups++
							vmsg &amp;quot;${indent}Search-Group: group $myadspath&amp;quot;
							Search-Group ( $indent + &amp;#39;  &amp;#39; ) $myadspath $dictionary
						}
					&amp;#39;contact&amp;#39;
						{
							if( $dictionary.$myadspath -eq 1 )
							{
								msg &amp;quot;${indent}Search-Group: continue duplicate contact&amp;quot;
								continue
							}
							$dictionary.$myadspath = 1
							$script:groupContacts++
							vmsg &amp;quot;${indent}Search-Group: contact $myadspath&amp;quot;
						}
					&amp;#39;user&amp;#39;
						{
							if( $dictionary.$myadspath -eq 1 )
							{
								msg &amp;quot;${indent}Search-Group: continue duplicate user&amp;quot;
								continue
							}
							$dictionary.$myadspath = 1
							$script:groupUsers++
							vmsg &amp;quot;${indent}Search-Group: user $myadspath&amp;quot;
						}
					&amp;#39;foreignSecurityPrincipal&amp;#39;
						{
							### do nothing
						}
					default
						{
							write-error &amp;quot;Search-Group: unhandled objectClass as member of group: $objectClass&amp;quot;
						}
				}
			}

			### could just say: $quitLoop = $lastLoop
			### but it&amp;#39;s not a worthwhile optimization
			### (due to a loss of clarity in WHY)

			if( $lastLoop -eq $true )
			{
				msg &amp;quot;${indent}Search-Group: set quitLoop = true&amp;quot;
				$quitLoop = $true
			}
		}
		else
		{
			if( $lastLoop -eq $true )
			{
				msg &amp;quot;${indent}Search-Group: set quitLoop = true&amp;quot;
				$quitLoop = $true
			}
			else
			{
				msg &amp;quot;${indent}Search-Group: set lastLoop = true&amp;quot;
				$lastLoop = $true
			}
		}

		if( $lastLoop -eq $false )
		{
			msg &amp;quot;${indent}Search-Group: old low = $low, old high = $high&amp;quot;
			$low  = $high + 1
			$high = $low  + $step
			msg &amp;quot;${indent}Search-Group: new low = $low, new high = $high&amp;quot;
		}

	} until( $quitLoop -eq $true )

	$object   = $null
	$searcher = $null
	$groupDE  = $null

	$primaryID = Get-PrimaryGroupId $indent $ADSpath
	if( $primaryID -gt 0 )
	{
		Search-PrimaryGroupId $indent $script:defaultNC $primaryId $dictionary
	}

	msg &amp;quot;${indent}Search-Group: exit, $ADSpath&amp;quot;
}

function Search-ADForGroup
{
	Param(
		[string]$indent,
		[string]$group
	)

	msg &amp;quot;${indent}Search-ADForGroup: enter, group = $group&amp;quot;

	### build the LDAP search to find the group distinguishedName from the provided short name

	$rootDSE    = [ADSI]&amp;quot;LDAP://RootDSE&amp;quot;
	$defaultNC  = $rootDSE.defaultNamingContext
	$ldapFilter = &amp;quot;(&amp;amp;(objectCategory=group)(name=$group))&amp;quot;
	$rootDSE    = $null

	$directorySearcher = New-Object System.DirectoryServices.DirectorySearcher
	$directorySearcher.PageSize    = 1000
	$directorySearcher.SearchRoot  = ( &amp;quot;LDAP://&amp;quot; + $defaultNC )
	$directorySearcher.SearchScope = &amp;quot;subtree&amp;quot;
	$directorySearcher.Filter      = $ldapFilter

	### Define the property we want (if we don&amp;#39;t specify at least one property,
	### then &amp;quot;all default properties&amp;quot; get loaded - that&amp;#39;s slower).

	$directorySearcher.PropertiesToLoad.Add( &amp;quot;distinguishedName&amp;quot;  ) | Out-Null

	$groups = $directorySearcher.FindAll()
	if( $groups -eq $null )
	{
		write-error &amp;quot;Search-ADForGroup: No such group found: $group&amp;quot;
	}
	elseif( $groups.Count -eq 1 )
	{
		$script:defaultNC = $defaultNC
		msg &amp;quot;${indent}Search-ADForGroup: exit, $($groups.Item( 0 ).Path)&amp;quot;

		return $groups.Item( 0 ).Path ### same as ADSpath in VBScript
	}
	else
	{
		write-error &amp;quot;Search-ADForGroup: Multiple groups were found that match: $group&amp;quot;
	}

	msg &amp;quot;${indent}Search-ADForGroup: exit, null&amp;quot;
	return $null
}

	###
	### Main
	###

	if( $VeryVerbose )
	{
		$Verbose = $true
	}

	$result = Search-ADForGroup &amp;#39;&amp;#39; $group
	if( $result -ne $null )
	{
		$dictionary = @{}

		Search-Group &amp;#39;&amp;#39; $result $dictionary

		if( $Statistics )
		{
			write-host &amp;quot; &amp;quot;
			write-host &amp;quot;Users: $($script:groupUsers)&amp;quot;
			write-host &amp;quot;Groups: $($script:groupGroups)&amp;quot;
			write-host &amp;quot;Contacts: $($script:groupContacts)&amp;quot;
		}

		$dictionary = $null
	}
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6979" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Creating Lots of Exchange Mailboxes for Testing Purposes</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/05/02/creating-lots-of-exchange-mailboxes-for-testing-purposes.aspx</link><pubDate>Wed, 02 May 2012 12:51:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6978</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;I&amp;#39;m in the process of developing a large new script and to test it properly, I needed to create several thousand new users with mailboxes. While I&amp;#39;ve previously written scripts like this for Exchange 2003, I didn&amp;#39;t have one handy for Exchange 2007 and later.&lt;/p&gt;&lt;p&gt;So I whipped one up and decided to share! As you can probably tell, this script will create, by default, 6,000 users and mailboxes. You do have to tell it the password to assign, and you should change the $upnDomain to be a proper choice for your environment.&lt;/p&gt;&lt;p&gt;Enjoy!&lt;/p&gt;&lt;p&gt;
&lt;font color="green"&gt;
&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;Param(
	[int]$minValue =    1,
	[int]$maxValue = 6000,
	[string]$upnDomain = &amp;#39;smithcons.local&amp;#39;
)

$password = ( get-credential UserName-Not-Important ).password

for( $i = $minValue; $i -le $maxValue; $i++ )
{
	$user = &amp;quot;User&amp;quot; + $i.ToString()
	New-Mailbox $user `
		-alias $user `
		-userprincipalname ( $user + &amp;#39;@&amp;#39; + $upnDomain ) `
		-samaccountname $user `
		-firstname $user `
		-lastname $user `
		-password $password
}

&amp;#39;Done&amp;#39;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6978" width="1" height="1"&gt;</description></item><item><title>How to Make Your PowerShell Session into an Exchange Management Shell</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/04/27/how-to-make-your-powershell-session-into-an-exchange-management-shell.aspx</link><pubDate>Fri, 27 Apr 2012 13:00:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6977</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;In my last blog post, &lt;a title="Determining the Exchange Version without Using Get-ExchangeServer" href="http://theessentialexchange.com/blogs/michael/archive/2012/04/25/determining-the-exchange-version-without-using-get-exchangeserver.aspx" target="_blank"&gt;Determining the Exchange Version Without Using Get-ExchangeServer&lt;/a&gt;,I showed how to read the Exchange version from the registry. Shortly after posting that, the questions began to roll in about &amp;quot;how to make my PowerShell session behave like the Exchange Management Shell&amp;quot;.&lt;/p&gt;&lt;p&gt;Well, it&amp;#39;s pretty easy.&lt;/p&gt;&lt;p&gt;In the New-ExchangeConnection function, I use &amp;quot;dot-sourcing&amp;quot; to minimize the work. When you dot-source something, it means you are basically including the contents of the file into YOUR file, at that location. So, I use the same setup that EMS uses; just changed to work on the proper version of Exchange, plus the little extra magic that usually happens behind the scenes, but doesn&amp;#39;t because it&amp;#39;s happening in my script instead of in the &amp;quot;real&amp;quot; EMS.&lt;/p&gt;&lt;p&gt;So here it is!&lt;/p&gt;
&lt;p&gt;
&lt;font color="green"&gt;
&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;###
### New-ExchangeConnection
###
### Create a connection to an Exchange server, in a version
### appropriate way.
###

function New-ExchangeConnection
{
	### Load the versioning function

	. .\Get-ExchangeVersion.ps1

	$exchangeVersion = Get-ExchangeVersion
	if( $exchangeVersion )
	{
		switch ( $exchangeVersion.Version )
		{
			&amp;#39;2007&amp;#39;
				{
					### This first segment is handled by a PSConsole file in EMS itself
					Get-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin -EA 0
					if ($?)
					{
						## snap-in already loaded, do nothing
					}
					else
					{
						Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
					}

					### This line makes the rest of the magic happen
					. &amp;#39;C:\Program Files\Microsoft\Exchange Server\bin\Exchange.ps1&amp;#39;


					return 1
				}
			&amp;#39;2010&amp;#39;
				{
					### with the advent of remote PowerShell in E14, almost all
					### of the magic gets hidden
					$credential = $null
					$global:remoteSession = $null
					. &amp;#39;C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1&amp;#39;
					Connect-ExchangeServer -auto

					return 1
				}
			default
				{
					write-error &amp;quot;Unsupported version $($exchangeVersion.Version)&amp;quot;
					return 0
				}
		}
	}

	write-error &amp;quot;This is not an Exchange server&amp;quot;
	return 0
}
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6977" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Determining the Exchange Version - without using Get-ExchangeServer</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/04/25/determining-the-exchange-version-without-using-get-exchangeserver.aspx</link><pubDate>Wed, 25 Apr 2012 20:57:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6976</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;If you write lots of Exchange scripts (as I do), and have several different versions of Exchange on which you need to run those scripts (as I do); you soon see the need for being able to determine what version of Exchange a particular server is running - and you may need to do this outside of the Exchange Management Shell.&lt;/p&gt;&lt;p&gt;This may be necessary because of different behaviors that are required with the Exchange Management Shell depending on the version of Exchange. It also may be required because the version of Exchange pre-dates the Exchange Management Shell (i.e., Exchange 2003). As an additional rationale, your script may need to load the Exchange Management Shell and cannot do that properly without knowing the version of Exchange that is being targeted (the process differs between Exchange 2007 and Exchange 2010).&lt;/p&gt;&lt;p&gt;Thus, I&amp;#39;ve written a couple of functions that I wrap in a script to give me that information. Get-ExchangeServer, the function, returns a simple object containing the name of the server examined, plus the version of Exchange that is running on that server. That is:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;struct result {&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;string Name;&lt;/p&gt;&lt;p&gt;string Version;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;}&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;If the result of the function is $null, then the version could not be determined and the server targeted either does not exist or is (very likely) not running Exchange. If the server does not exist (or the firewall on the server prevents remote management via WMI) then an error is displayed.&lt;/p&gt;&lt;p&gt;The version information about Exchange is stored in the registry of each Exchange server. The script below shows some techniques for accessing the registry to obtain string (reg_sz) and&amp;nbsp;short integer&amp;nbsp;(reg_dword) values while using PowerShell. Note that PowerShell has multiple mechanisms for accessing this information, including the so-called Registry Provider. I personally find using the WMI functions to be a bit easier to handle.&lt;/p&gt;&lt;p&gt;You can include these functions directly into your PowerShell profile, or you can dot-source the function on an as-needed basis.&lt;/p&gt;&lt;p&gt;Without further ado, enjoy!&lt;/p&gt;
&lt;p&gt;
&lt;font color="green"&gt;
&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;###
### Get-ExchangeVersion
###
### Return the version of the specified Exchange server
###

Set-StrictMode -Version 2.0

$HKCR = 2147483648
$HKCU = 2147483649
$HKLM = 2147483650

function RegRead
{
	Param(
		[string]$computer,
		[int64] $hive,
		[string]$keyName,
		[string]$valueName,
		[ref]   $value,
		[string]$type = &amp;#39;reg_sz&amp;#39;
	)

	try {
		$wmi = [wmiclass]&amp;quot;\\$computer\root\default:StdRegProv&amp;quot;
		if( $wmi -eq $null )
		{
			return 1
		}
	}
	catch {
		##$error[0]
		write-error &amp;quot;Could not open WMI access to $computer&amp;quot;
		return 1
	}

	switch ( $type )
	{
		&amp;#39;reg_sz&amp;#39;
			{
				$r = $wmi.GetStringValue( $hive, $keyName, $valueName )
				$value.Value = $r.sValue
			}
		&amp;#39;reg_dword&amp;#39;
			{
				$r = $wmi.GetDWORDValue( $hive, $keyName, $valueName )
				$value.Value = $r.uValue
			}
		default
			{
				write-error &amp;quot;Unsupported type: $type&amp;quot;
			}
	}

	$wmi = $null

	return $r.ReturnValue
}

function Get-ExchangeVersion
{
	Param(
		[string]$computer = &amp;#39;.&amp;#39;
	)

	if( $computer -eq &amp;#39;.&amp;#39; )
	{
		$computer = $env:ComputerName
	}

	## Exchange vNext (assumption!)
	## HKLM\Software\Microsoft\ExchangeServer\v15\Setup
	## MsiProductMajor (DWORD 15)

	## Exchange 2010
	## HKLM\Software\Microsoft\ExchangeServer\v14\Setup
	## MsiProductMajor (DWORD 14)

	## Exchange 2007
	## HKLM\SOFTWARE\Microsoft\Exchange\Setup
	## MsiProductMajor (DWORD 8)

	## Exchange 2003
	## HKLM\SOFTWARE\Microsoft\Exchange\Setup
	## Services Version (DWORD 65)

	$v = 0

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\ExchangeServer\v15\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 15 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;vNext&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\ExchangeServer\v14\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 14 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2010&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\Exchange\Setup&amp;#39; &amp;#39;MsiProductMajor&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 8 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2007&amp;#39;
		return $obj
	}

	$i = RegRead $computer $HKLM &amp;#39;Software\Microsoft\Exchange\Setup&amp;#39; &amp;#39;Services Version&amp;#39; ( [ref] $v ) &amp;#39;reg_dword&amp;#39;
	if( ( $i -eq 0 ) -and ( $v -eq 65 ) )
	{
		$obj = &amp;quot;&amp;quot; | Select Name, Version
		$obj.Name = $computer
		$obj.Version = &amp;#39;2003&amp;#39;
		return $obj
	}

	### almost certainly not an Exchange server

	return $null
}
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6976" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Registry/default.aspx">Registry</category></item><item><title>Finding Duplicate IP Addresses and Duplicate Names in a DNS Zone</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/04/24/finding-duplicate-ip-addresses-and-duplicate-names-in-a-dns-zone.aspx</link><pubDate>Tue, 24 Apr 2012 20:53:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6975</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;One of the traditional issues associated with cleaning up an Active Directory Directory Services (AD DS) domain in DNS is to ensure that duplicate names in DNS are removed (this is typically an issue caused by not having DNS Scavenging enabled, or by having hosts forcefully removed from the domain and not properly cleaning up DNS). As a corollary, this can also lead to duplication of manually assigned IP addresses, regardless of whether those IP addresses are IPv4 or IPv6.&lt;/p&gt;&lt;p&gt;Duplications&amp;nbsp;can cause issues for many different servers and services, including AD DS, Exchange, SharePoint, etc.&lt;/p&gt;&lt;p&gt;I&amp;#39;ve written a PowerShell script that can help you determine the duplicates in order to clean those up. See the script below!&lt;/p&gt;&lt;font color="green"&gt;&lt;pre&gt;&lt;blockquote&gt;##
## build-dns-objects.ps1
##

##
## Michael B. Smith
## michael (at) TheEssentialExchange.com
## April, 2012
##

##
## Primary functionality:
##
## Based on either an input file or the output of a default command:
##
## 	dnscmd ( $env:LogonServer ).SubString( 2 ) /enumrecords $env:UserDnsDomain &amp;quot;@&amp;quot;
##
## Create an array containing all of the DNS objects describing the input.
##
## ----
##
## Secondary functionality:
##
## Find all the duplicate IP addresses and the duplicate names 
## contained within either the file or the command output.
##
## By specifying the -skipRoot option, all records for the root of
## the domain are ignored.
##

##
## General record format returned by DNScmd.exe:
##
##	name
##	[aging:xxxxxxxx] 
##	TTL
##	resource-record-type
##	value
##	[optional additional values]
##
## Fields may be separated by one-or-more spaces or one-or-more tabs
## [aging:xxxxxxxx] fields are optional
##

Param(
	[string]$filename,
	[switch]$skipRoot
)

function new-dns-object
{
	return ( &amp;quot;&amp;quot; | Select Name, Aging, TTL, RRtype, Value )
}

function tmpFileName
{
	[string] $strFile = ( Join-Path $Env:Temp ( Get-Random ) ) + &amp;quot;.txt&amp;quot;

	if( ( Test-Path -Path $strFile -PathType Leaf ) )
	{
		rm $strNetworkFile -EA 0
		if( $? )
		{
		##	write-output &amp;quot;...file was deleted&amp;quot;
		}
		else
		{
		##	write-output &amp;quot;...couldn&amp;#39;t delete file, error: $($error[0].ToString())&amp;quot;
		}
	}

	return $strFile
}

if( $filename -and ( $filename.Length -gt 0 ) )
{
	$tmp = $filename
}
else
{
	$tmp = tmpFileName
	dnscmd ( $env:LogonServer ).SubString( 2 ) /enumrecords $env:UserDnsDomain &amp;quot;@&amp;quot; &amp;gt;$tmp
}

$objects = @()
$records = gc $tmp

$script:zone = &amp;#39;&amp;#39;

## Primary functionality:

foreach( $record in $records )
{
	## write-output &amp;quot;Processing: $record&amp;quot;

	if( !$record )
	{
		continue
	}
	if( $record -eq &amp;quot;Returned records:&amp;quot; )
	{
		continue
	}
	if( $record -eq &amp;quot;Command completed successfully.&amp;quot; )
	{
		continue
	}

	$firstChar = $record.SubString( 0, 1 )
	$record = $record.Trim()

	if( $record.Length -eq 0 )
	{
		continue
	}

	$object = new-dns-object

	$index = 0

	$record = $record.Replace( &amp;quot;`t&amp;quot;, &amp;quot; &amp;quot; )

	$array = $record.Split( &amp;#39; &amp;#39; )
	if( ( $firstchar -eq &amp;quot; &amp;quot; ) -or ( $firstchar -eq &amp;quot;`t&amp;quot; ) )
	{
		$object.Name = $script:Zone
	}
	else
	{
		$object.Name = $array[ 0 ]
		$script:Zone = $array[ 0 ]
		$index++
	}

	if( ( $array[ $index ].Length –ge 3 ) –and ( $array[ $index ].SubString( 0, 3 ) –eq “[Ag” ) )  ## [Aging:3604987]
	{
		$object.Aging = $array[ $index ]
		$index++
	}

	$object.TTL    = $array[ $index ]
	$object.RRType = $array[ $index + 1 ]
	$object.Value  = $array[ $index + 2 ]

	$objects += $object
}

## Secondary functionality:

## There are more efficient ways to do this, but this is easy.

## search for duplicate names

$hash = @{}
foreach( $o in $objects )
{
	if( $o.RRtype -eq &amp;quot;A&amp;quot; )
	{
		$name = $o.Name
		if( $skipRoot -and ( $name -eq &amp;quot;@&amp;quot; ) )
		{
			continue
		}
		if( $hash.$name )
		{
			&amp;quot;Duplicate name: $name, IP: $($o.Value), original IP: $($hash.$name)&amp;quot;
		}
		else
		{
			$hash.$name = $o.Value
		}
	}
}
$hash = $null

## search for duplicate IP addresses

$hash = @{}
foreach( $o in $objects )
{
	if( $o.RRtype -eq &amp;quot;A&amp;quot; )
	{
		if( $skipRoot -and ( $o.Name -eq &amp;quot;@&amp;quot; ) )
		{
			continue
		}

		$ip = $o.Value
		if( $hash.$ip )
		{
			&amp;quot;Duplicate IP: $ip, name: $($o.Name), original name: $($hash.$ip)&amp;quot;
		}
		else
		{
			$hash.$ip = $o.Name
		}
	}
}
$hash = $null

&amp;quot; &amp;quot;
&amp;quot;Done&amp;quot;
&lt;/blockquote&gt;
&lt;/pre&gt;
&lt;/font&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6975" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/DNS/default.aspx">DNS</category></item><item><title>Enumerating networks and building routes with PowerShell</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/04/10/enumerating-networks-and-building-routes-with-powershell.aspx</link><pubDate>Tue, 10 Apr 2012 12:38:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6974</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;I&amp;#39;ve been working with a company that is in the process of setting up a remote datacenter for disaster recovery. They brought me in to help design their Exchange cross-site resilience, and I&amp;#39;ve been helping them with a few other things too.&lt;/p&gt;&lt;p&gt;Primary connectivity between the primary datacenter and the remote datacenter is via an L2TP tunnel, nailed up and encrypted, across the public Internet, using RRAS.&lt;/p&gt;&lt;p&gt;With over 300 servers in the primary datacenter and over 100 servers targeted in the remote datacenter, we needed a way to set up the proper routes for each server farm to access the other (in both cases, the default gateway is a TMG array).&lt;/p&gt;&lt;p&gt;I decided that I could do that in a few lines of PowerShell. Truth be told, the basic functionality is pretty simple - you iterate through the computers you are examining, look at the wired network interfaces on those computers, see those that have a particular source IP address, and give them a route to the destination network.&lt;/p&gt;&lt;p&gt;However - and as always, the devil is in the details - It Ain&amp;#39;t That Simple (IATS - HAH!).&lt;/p&gt;&lt;p&gt;First of all, Active Directory was used as the source of computers. That&amp;#39;s great! However, there are computers in AD that aren&amp;#39;t in DNS. I have no clue how that happened, but one can surmise that the computers were cut off, their IP addresses reassigned, and the computer object never removed from AD. That&amp;#39;s one condition we have to deal with.&lt;/p&gt;&lt;p&gt;Secondly, as a corollary to the above, workgroup computers aren&amp;#39;t found at all. With the solution presented here, there were already DNS entries for these computers (and there were only a handful of them), so we temporarily created computer objects for them in AD.&lt;/p&gt;&lt;p&gt;Third, we found some computers have bad IP addresses (specifically 0.0.0.0). Again, I have no clue how that happened, but those have to be filtered out.&lt;/p&gt;&lt;p&gt;Fourth, group policies in this organization aren&amp;#39;t applied very logically. That&amp;#39;s not what they called me in for, so I had little say. Regardless, a number of computer did not have remote management enabled, thus preventing remote examination of their configuration and remote changes to their configuration. Detection of computers without remote management thus became something the script had to deal with.&lt;/p&gt;&lt;p&gt;Fifth, we had first decided to use &amp;#39;ping&amp;#39; to determine the accessibility of the computers as part of the discovery process. Well, fewer than half of the computers allowed &amp;#39;ping&amp;#39; through their firewalls, so that solution had to be summarily discarded. In order to determine accessibility, we actually had to attempt to access the computers.&lt;/p&gt;&lt;p&gt;Sixth, and this is actually a downstream symptom of the issue above, on some servers remote management generates an error. Those servers need to be detected and repaired.&lt;/p&gt;&lt;p&gt;Seventh, and finally, some servers have multiple IP addresses. This may mean that they are already being used as RRAS servers and they need human intelligence to determine whether a route change should be applied.&lt;/p&gt;&lt;p&gt;I didn&amp;#39;t use &amp;quot;route add&amp;quot; because in some situations &amp;quot;route add&amp;quot; is known to fail, and &amp;quot;netsh interface ipv4 add route&amp;quot; is preferred.&lt;/p&gt;&lt;p&gt;The resulting utility script is not particularly pretty. But it&amp;#39;s very useful and the techniques illustrated are likely to be useful for you in your scripting needs too. So, I present it for your pleasure. :-)&lt;/p&gt;
&lt;p&gt;
&lt;font color="green"&gt;
&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;[string] $nl = &amp;quot;`r`n&amp;quot;

$HKCR = 2147483648
$HKCU = 2147483649
$HKLM = 2147483650

$script:badIP         = @()
$script:commands      = @()
$script:cannotping    = @()
$script:cannotresolve = @()
$script:cannotmanage  = @()
$script:warningmsg    = @()

function msg
{
	$str = &amp;#39;&amp;#39;

	foreach( $arg in $args )
	{
		$str += $arg + &amp;#39; &amp;#39;
	}
	Write-Host $str
}

function RegRead
{
	Param(
		[string]$computer,
		[int64] $hive,
		[string]$keyName,
		[string]$valueName,
		[ref]   $value,
		[string]$type = &amp;quot;REG_SZ&amp;quot;
	)

	$wmi = [wmiclass]&amp;quot;\\$computer\root\default:StdRegProv&amp;quot;
	if( $wmi -eq $null )
	{
		$script:cannotmanage += $computer
		return 1
	}

	$r = $wmi.GetStringValue( $hive, $keyName, $valueName )
	$value.Value = $r.sValue

	$wmi = $null

	return $r.ReturnValue
}


function test-ping( [string]$server )
{
	[string]$routine = &amp;quot;test-ping:&amp;quot;

	trap 
	{
		# we should only get here if the New-Object fails.

		write-error &amp;quot;$routine Cannot create System.Net.NetworkInformation.Ping for $server.&amp;quot;
		return $false
	}

	$ping = New-Object System.Net.NetworkInformation.Ping
	if( $ping )
	{
		trap [System.Management.Automation.MethodInvocationException] 
		{
			###write-error &amp;quot;$routine Invalid hostname specified (cannot resolve $server).&amp;quot;
			msg &amp;quot;...cannot resolve $server in DNS&amp;quot;
			$script:cannotresolve += $server
			return $false
		}

		for( $i = 1; $i -le 2; $i++ )
		{
			$rslt = $ping.Send( $server )
			if( $rslt -and ( $rslt.Status -eq [System.Net.NetworkInformation.IPStatus]::Success ) )
			{
				### msg &amp;quot;$routine Can ping $server. Successful on attempt $i.&amp;quot;
				$ping = $null
				return $true
			}
			sleep -seconds 1
		}
		$ping = $null
	}

	###write-error &amp;quot;$routine Cannot ping $server. Failed after 5 attempts.&amp;quot;
	msg &amp;quot;...cannot ping $server&amp;quot;
	$script:cannotping += $server

	return $false
}

### 
### Main
###

$start = (get-date).DateTime.ToString()
msg &amp;#39;Begin&amp;#39; $start

ipmo ActiveDirectory
$computers = Get-AdComputer -Filter * -SearchBase &amp;quot;OU=Servers,DC=example,DC=com&amp;quot; -ResultSetSize $null | Select DnsHostName
msg &amp;quot;There are $($computers.Count) computers to check&amp;quot;
$computerCount = 0
foreach( $computer in $computers)
{
	$computerName = $computer.DnsHostName
	$computerCount++
	msg &amp;quot;Checking $computername... ($computerCount of $($computers.Count))&amp;quot;

#	if( -not ( test-ping $computerName ) )
#	{
#		###msg &amp;#39;...cannot resolve or ping&amp;#39;
#		msg &amp;#39; &amp;#39;
#		continue
#	}

	### server IP addresses - I only care about IPv4
	$serverIPv4   = @()
	$serverIPname = @()

	$nicSettings = gwmi Win32_NetworkAdapterSetting -EA 0 -ComputerName $computerName
	if( $nicSettings -eq $null )
	{
		msg &amp;quot;...cannot access $computername&amp;quot;
		msg &amp;quot; &amp;quot;
		$script:cannotresolve += $computername
		continue
	}

	foreach( $nicSetting in $nicSettings )
	{
		### msg &amp;quot;Element=&amp;quot; $nicSetting.Element ## is of type Win32_NetworkAdapter
		if( $nicSetting.Element -eq $null )
		{
			msg &amp;quot;...netSetting.Element is null&amp;quot;
			continue
		}

		$nicElement = [wmi] $nicSetting.Element
		if( $nicElement -eq $null )
		{
			msg &amp;quot;...nicElement is null&amp;quot;
			continue
		}

		if( $nicElement.AdapterType -eq &amp;quot;Ethernet 802.3&amp;quot; )
		{
			### msg &amp;quot;Setting=&amp;quot; $nicSetting.Setting
			$nicConfig = [wmi] $nicSetting.Setting ## is of type Win32_NetworkAdapterConfiguration
			$nicGUID = $nicConfig.SettingID
			### msg &amp;quot;NicGUID=&amp;quot; $nicGUID
			### msg &amp;quot;NIC IPEnabled=&amp;quot; $nicConfig.IPEnabled.ToString()
			if( $nicConfig.IPEnabled -eq $true )
			{
				$returnValue = $true ## there is at least one valid NIC

				$hive = $HKLM
				$keyName = &amp;quot;System\CurrentControlSet\Control\Network\&amp;quot; +
					&amp;quot;{4D36E972-E325-11CE-BFC1-08002BE10318}\&amp;quot; + 
					$nicGUID + 
					&amp;quot;\Connection&amp;quot;
				$valueName = &amp;quot;Name&amp;quot;

				$name = &amp;#39;&amp;#39;
				$result = RegRead $computerName $hive $keyName $valueName ( [ref] $name ) &amp;#39;reg_sz&amp;#39;
				if( $result -ne 0 )
				{
					$name = &amp;quot;&amp;quot;
				}

				###msg &amp;quot;NIC name:&amp;quot; $name

				$script:arrNicName += $name
				foreach( $ip in $nicConfig.IPAddress )
				{
					if( $ip.IndexOf( &amp;#39;:&amp;#39; ) -ge 0 )
					{
						$script:arrIPListPublic_v6 += $ip
						###msg &amp;quot;IPv6 address &amp;quot; $ip
					}
					else
					{
						$script:arrIPListPublic_v4 += $ip
						$serverIPv4   += $ip
						$serverIPname += $name
						###msg &amp;quot;IPv4 address:&amp;quot; $ip
					}
				}
			}
			$nicConfig = $null
		}
		$nicElement = $null
	}
	$nicSettings = $null

	$limit = $serverIPv4.Count - 1

	$allowedAddresses = 0
	for( $i = 0; $i -le $limit; $i++ )
	{
		$ip = $serverIPv4[ $i ]
		$name = $serverIPname[ $i ]

		if( $ip.Length -lt 10 )
		{
			$script:badIP += &amp;quot;$computerName $name $ip&amp;quot;
		}
		elseif( $ip.SubString( 0, 10 ) -eq &amp;quot;10.129.59.&amp;quot; )
		{
			msg &amp;quot;*** YES - $computerName is on the server room secure network using NIC &amp;#39;$name&amp;#39; ***&amp;quot;
			$cmd = &amp;#39;netsh -r &amp;#39; + $computerName + 
				&amp;#39; interface ipv4 add route 10.129.68.0/22 &amp;quot;&amp;#39; + $name + &amp;#39;&amp;quot; 10.129.59.104&amp;#39;
			$script:commands += $cmd
			$allowedAddresses++
		}
		elseif( $ip.SubString( 0, 10 ) -eq &amp;quot;10.129.68.&amp;quot; )
		{
			msg &amp;quot;*** YES - $computerName is on the datacenter network using NIC &amp;#39;$name&amp;#39; ***&amp;quot;
			$cmd = &amp;#39;netsh -r &amp;#39; + $computerName + 
				&amp;#39; interface ipv4 add route 10.129.59.0/20 &amp;quot;&amp;#39; + $name +&amp;#39;&amp;quot; 10.129.68.7&amp;#39;
			$script:commands += $cmd
			$allowedAddresses++
		}
	}
	if( $allowedaddresses -gt 1 )
	{
		msg &amp;quot;*** WARNING ***&amp;quot;
		$m = &amp;quot;$computerName has more than one IPv4 address. It may require extra configuration.&amp;quot;
		msg $m
		$script:warningmsg += $m
		msg &amp;quot;*** WARNING ***&amp;quot;
	}

	if( $allowedaddresses -eq 0 )
	{
		msg &amp;quot;...no 10.129.59.0/24 or 10.129.68.0/24 IP addresses found on this computer (out of $($serverIPv4.Count))&amp;quot;
	}

	msg &amp;quot; &amp;quot;
}

msg &amp;quot;List of computers and NICs with bad IP addresses ($($script:badIP.Count))&amp;quot;
$script:badIP
&amp;#39; &amp;#39;

msg &amp;quot;List of computers in AD who cannot be accessed ($($script:cannotresolve.Count))&amp;quot;
$script:cannotresolve
&amp;#39; &amp;#39;

#msg &amp;quot;List of computers in AD who cannot be pinged ($($script:cannotping.Count))&amp;quot;
#$script:cannotping
#&amp;#39; &amp;#39;

msg &amp;quot;List of computer in AD who cannot be managed ($($script:cannotmanage.Count))&amp;quot;
$script:cannotmanage
&amp;#39; &amp;#39;

msg &amp;quot;Warning messages ($($script:warningmsg.Count))&amp;quot;
$script:warningmsg
&amp;#39; &amp;#39;

msg &amp;quot;Full list of routing commands ($($script:commands.Count))&amp;quot;
$script:commands
&amp;#39; &amp;#39;

msg &amp;#39;Began at&amp;#39; $start
msg &amp;#39;Done at&amp;#39; (get-date).DateTime.ToString()
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;

&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6974" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>System Center 2012</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/04/02/system-center-2012.aspx</link><pubDate>Mon, 02 Apr 2012 19:51:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6973</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;The System Center 2012 suite released to web (RTW&amp;#39;ed) today.&lt;/p&gt;&lt;p&gt;The suite will be officially announced at the &lt;a title="Microsoft Management Summit" href="http://www.mms-2012.com" target="_blank"&gt;Microsoft Management Summit&lt;/a&gt; on April 17th.&lt;/p&gt;&lt;p&gt;This includes Operations Manager, Configuration Manager, Service Manager, Data Protection Manager, Orchestrator, App Controller, and Virtual Machine Manager.&lt;/p&gt;&lt;p&gt;As some of you are aware, in additional to my Exchange and Active Directory work; I also develop and deliver customer System Center classes for customers all over the world. I just finished my first System Center 2012 Configuration Manager course last week (the last week of March 2012) and began my first System Center 2012 Operations Manager course today (April 2, 2012).&lt;/p&gt;&lt;p&gt;The new versions are evolutionary steps forward and include some greatly improved ease of use and integration features.&lt;/p&gt;&lt;p&gt;If you have access to MSDN, TechNet, Volume Licensing, or Action Pack; the System Center suite should be available for you to download and evaluate. Go check them out!&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6973" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/System+Center/default.aspx">System Center</category></item><item><title>Enumerating IP Addresses on Network Adapters using PowerShell</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/02/28/enumerating-ip-addresses-on-network-adapters-using-powershell.aspx</link><pubDate>Wed, 29 Feb 2012 00:48:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6972</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Regardless of whether&amp;nbsp;a server is for Exchange, for SQL, or for any other use; a key component of the server&amp;#39;s configuration is the set of IP addresses assigned to it.&lt;/p&gt;&lt;p&gt;Many organizations apply all addresses (even server addresses) using DHCP. For servers, this is often based on&amp;nbsp;reservations of the MAC addresses on those servers. In most cases, this works well. However, if the DHCP server(s) should be unavailable (for whatever reason), the server will not receive the desired address, and instead will receive an APIPA address (that is, an IPv4 address starting with &amp;quot;169.&amp;quot; as the first octet of the IPv4 address).&lt;/p&gt;&lt;p&gt;Regardless of the result, for many servers it is important to know if the IP addresses should change. The first step in that process is to be able to enumerate (list) all of the IP addresses for all of the network interfaces on the server.&lt;/p&gt;&lt;p&gt;There are a number of mechanisms available for obtaining this information from Windows; including but not limited to &amp;quot;ipconfig.exe&amp;quot;, WMI, &amp;quot;wmic.exe&amp;quot;, &amp;quot;netsh.exe&amp;quot;, and others. The challenge for using most of the available interfaces is that they provide a significant amount of extraneous information. Extraneous information makes the output very difficult to parse and use within scripts.&lt;/p&gt;&lt;p&gt;This blog post provides a script that enumerates all of the network interfaces available on a particular computer and the associated IP addresses. The second script, which you could schedule to run every 15-30 minutes, compares and contrasts any changes in IP addresses on a server and reports the change via a SMTP server (such as Exchange Server).&lt;/p&gt;&lt;p&gt;The first script:&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;###
### IP-Address-Check.ps1
###
### This script lists all Ethernet adapters supporting IP on a computer and
### the associated IP addresses - both IPv4 and IPv6. A simple object is
### output containing the name of the interface, an array containing the IPv4
### addresses, and an array containing the IPv6 addresses.
###
### While this information is available from netsh.exe, the format of the
### output from netsh.exe is not conducive for easy re-use or parsing.
###
### Michael B. Smith
### michael at smithcons dot com
### http://TheEssentialExchange.com
### February 28, 2012
###

Set-StrictMode -Version 2.0

$HKLM = 2147483650
$wmi  = [wmiclass]&amp;quot;\root\default:StdRegProv&amp;quot;

function RegRead
{
	Param(
		[int64] $hive,
		[string]$keyName,
		[string]$valueName,
		[ref]   $value,
		[string]$type = &amp;quot;REG_SZ&amp;quot;
	)

	switch ( $type )
	{
		&amp;#39;reg_sz&amp;#39;
			{
				$r = $wmi.GetStringValue( $hive, $keyName, $valueName )
				$value.Value = $r.sValue
			}
		&amp;#39;reg_dword&amp;#39;
			{
				$r = $wmi.GetDWORDValue( $hive, $keyName, $valueName )
				$value.Value = $r.uValue
			}
		&amp;#39;reg_qword&amp;#39;
			{
				$r = $wmi.GetQWORDValue( $hive, $keyName, $valueName )
				$value.Value = $r.uValue
			}
		&amp;#39;reg_binary&amp;#39;
			{
				$r = $wmi.GetBinaryValue( $hive, $keyName, $valueName )
				$value.Value = $r.uValue
			}
		default
			{
				write-error &amp;quot;ERROR: RegRead $hive $keyName $valueName&amp;quot;
				return 1
			}
	}

	return $r.ReturnValue
}

function EnumerateAdapters
{
	$nicSettings = gwmi Win32_NetworkAdapterSetting -EA 0
	foreach( $nicSetting in $nicSettings )
	{
		$nicElement = [wmi] $nicSetting.Element
		if( $nicElement.AdapterType -eq &amp;quot;Ethernet 802.3&amp;quot; )
		{
			$nicConfig = [wmi] $nicSetting.Setting ## is of type Win32_NetworkAdapterConfiguration
			$nicGUID = $nicConfig.SettingID
			if( $nicConfig.IPEnabled -eq $true )
			{
				$hive = $HKLM
				$keyName = &amp;quot;System\CurrentControlSet\Control\Network\&amp;quot; +
					&amp;quot;{4D36E972-E325-11CE-BFC1-08002BE10318}\&amp;quot; + 
					$nicGUID + 
					&amp;quot;\Connection&amp;quot;
				$valueName = &amp;quot;Name&amp;quot;

				$name = &amp;#39;&amp;#39;
				$result = RegRead $hive $keyName $valueName ( [ref] $name ) &amp;#39;reg_sz&amp;#39;
				if( $result -ne 0 )
				{
					$name = &amp;quot;&amp;quot;
				}

				$arrIPListPublic_v6 = @()
				$arrIPListPublic_v4 = @()

				foreach( $ip in $nicConfig.IPAddress )
				{
					if( $ip.IndexOf( &amp;#39;:&amp;#39; ) -ge 0 )
					{
						$arrIPListPublic_v6 += $ip
					}
					else
					{
						$arrIPListPublic_v4 += $ip
					}
				}

				$obj = &amp;quot;&amp;quot; | select Name, IPv4Addresses, IPv6Addresses
				$obj.Name = $name
				$obj.IPv4Addresses = $arrIPListPublic_v4
				$obj.IPv6Addresses = $arrIPListPublic_v6

				$obj  ### output the object to the pipeline

				$arrIPListPublic_v4 = $null
				$arrIPListPublic_v6 = $null
			}

			$nicConfig = $null
		}
		$nicElement = $null
	}
	$nicSettings = $null
}

###
### Main
###

EnumerateAdapters

&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;

&lt;p&gt;The second script:&lt;font color="green"&gt;&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;&lt;pre&gt;###
### IP-Check-Controller.ps1
###
### Check to see if any IP addresses on a computer have changed. This may 
### be relevant in many configurations based on DHCP.
###
### Michael B. Smith
### michael at smithcons dot com
### http://TheEssentialExchange.com
### February 28, 2012
###

Param(
	[string]$to  = &amp;quot;ip-report@example.com&amp;quot;,
	[string]$mx  = &amp;quot;mx.example.com&amp;quot;,
	[string]$sub = &amp;quot;The IP address information has changed at $($env:ComputerName)&amp;quot;,
	[string]$fr  = &amp;quot;ip-check-controller@example.local&amp;quot;
)

[string]$nl = &amp;quot;`r`n&amp;quot;

###
### Main
###

if( ( Test-Path &amp;quot;Addr-old.txt&amp;quot; -PathType Leaf ) )
{
	rm &amp;quot;Addr-old.txt&amp;quot;
}
if( ( Test-Path &amp;quot;Addr-new.txt&amp;quot; -PathType Leaf ) )
{
	mv &amp;quot;Addr-new.txt&amp;quot; &amp;quot;Addr-old.txt&amp;quot;
}

.\IP-Address-Check | out-file &amp;quot;Addr-new.txt&amp;quot;

$result = Compare-Object -ReferenceObject $(gc &amp;quot;Addr-old.txt&amp;quot;) -DifferenceObject $(gc &amp;quot;Addr-new.txt&amp;quot;) -PassThru
if( $result -eq $null )
{
	## no changes to file
	exit
}

### if we get here, the files are different

$text =
	&amp;quot;A change has occurred in the IP addresses or active adapters.$nl$nl&amp;quot;+
	&amp;quot;The changes found are:$nl&amp;quot;

foreach( $line in $result )
{
	$text += &amp;quot;`t&amp;quot; + $line + $nl
}

$text += $nl + &amp;quot;The new full configuration is:$nl&amp;quot;
	$lines = gc &amp;quot;Addr-New.txt&amp;quot;
	foreach( $line in $lines )
	{
		$text += &amp;quot;`t$line$nl&amp;quot;
	}

$text += $nl + &amp;quot;The old full configuration is:$nl&amp;quot;
	$lines = gc &amp;quot;Addr-Old.txt&amp;quot;
	foreach( $line in $lines )
	{
		$text += &amp;quot;`t$line$nl&amp;quot;
	}

Send-MailMessage -To $to -Subject $sub -From $fr -Body $text -SmtpServer $mx -Priority High

&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;

&lt;p&gt;Typically, instead of scheduling the second PowerShell script, you would schedule a BAT script, as shown below:&lt;font color="green"&gt;&lt;/font&gt;&lt;/p&gt;&lt;font color="green"&gt;&lt;blockquote&gt;
&lt;pre&gt;@echo off
REM
REM IP-Check-Controller.bat
REM
REM Michael B. Smith
REM michael at smithcons dot com
REM http://TheEssentialExchange.com
REM February 28, 2012
REM

powershell.exe -command &amp;quot;.\IP-Check-Controller.ps1&amp;quot;
&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;/font&gt;

&lt;p&gt;
Until next time... 
&lt;/p&gt;&lt;p&gt;If there are things you would like to see written about, please let me know.&lt;/p&gt;
&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6972" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Microsoft/default.aspx">Microsoft</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Sending an email to users whose password is about to expire, a PowerShell Rewrite</title><link>http://theessentialexchange.com/blogs/michael/archive/2012/01/17/sending-an-email-to-users-whose-password-is-about-to-expire-a-powershell-rewrite.aspx</link><pubDate>Tue, 17 Jan 2012 14:34:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6971</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;In September of 2005, I wrote a blog post named &amp;quot;&lt;a title="Sending an email to users whose password is about to expire" href="http://theessentialexchange.com/blogs/michael/archive/2007/11/13/sending-an-e-mail-to-users-whose-password-is-about-to-expire.aspx" target="_blank"&gt;Sending an e-mail to users whose password is about to expire&lt;/a&gt;&amp;quot;. Written in VBScript, it was one of my most popular blog posts of all time. I still have clients of mine that use it and I get occaisional email questions regarding it.&lt;/p&gt;
&lt;p&gt;However, it is certainly showing its age!&lt;/p&gt;
&lt;p&gt;There are other solutions available now, for free. However, the other solutions don&amp;#39;t meet all of my needs. (As always, I encourage you to choose the solution that best meets your needs.)&lt;/p&gt;
&lt;p&gt;In my case, I need to be able to support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;div&gt;Fast and efficient searching of Active Directory&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;Support for Fine Grained Password Policies (FGPPs, also known Password Settings Objects or PSOs)&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;Authenticated SMTP&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;SSL/TLS SMTP&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;Report to Administrative User only (via SMTP)&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;Report to Administrative User only (via console)&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;Report to end-user (via SMTP)&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The script in this blog post meets all of those needs. And, it is now written in PowerShell instead of VBScript.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;I also chose to use the .Net Framework (System.DirectoryServices) for access to Active Directory (as well as some ADSI), instead of using the Active Directory PowerShell cmdlets. This makes it possible to execute the script on pretty-much any domain-joined computer, instead of one that requires RSAT-ADDS to be installed. It also avoids some weirdness around certain values returned by the AD cmdlets not matching older cmdlets or the AD itself.&lt;/p&gt;
&lt;p&gt;This script is designed to work with PowerShell v2.0. The only PowerShell v2.0 feature is use of the Send-MailMessage cmdlet and using splatting to call Send-MailMessage. If you need this on PowerShell v1.0, you must just write a replacement for Send-MailMessage (use System.Net.Mail - it&amp;#39;s not a big deal).&lt;/p&gt;
&lt;p&gt;The script will work on any domain functional level (DFL). The DFL is relevant to whether Fine-Grained Password Policies (FGPP, also known as Password Settings Objects - PSOs) are in use or not. FGPPs can be used when the domain level is at Windows 2008 or higher.&lt;/p&gt;
&lt;p&gt;Coming in at 767 lines, this is the longest single PowerShell script I believe I&amp;#39;ve posted. But it&amp;#39;s well documented and hopefully self-descriptive. There are some fairly advanced&amp;nbsp;capabilities demonstrated in this script, so you may find it worthwhile to study it a bit.&amp;nbsp;If you have questions, let me know.&lt;/p&gt;
&lt;blockquote&gt;&lt;pre&gt;&lt;font color="green"&gt;###
### Send-MailToUsersWithExpiringPasswords
###
### The top third of the script is data acquisition (and well documented).
### The bottom two-thirds is simple email-sending and report writing.
###
### This is PowerShell v1 compatible EXCEPT for using Send-MailMessage. You can
### easily replace that using System.Net.Mail if you wish.
###
### Parameter information:
###	daysForEmail   - how many days before a password expires should a user receive warning emails
###	adminEmail     - the administrator&amp;#39;s email address
###	adminEmailOnly - do not send email to users, only report to the administrator
###	SMTPfrom       - the From address for the SMTP message(s)
###	SMTPserver     - the server to be used for sending the SMTP message(s)
###	SMTPuser       - if credentials are required, the user for authenticating to the SMTP server
###	SMTPpassword   - if credentials are required, the password for the SMTPuser
###	anr            - instead of searching all users, only search for users matching the specified ANR string
###	SMTPuseSSL     - use an SSL/TLS connection, not a clear-text SMTP connection
###	Quiet          - if NOT set, a copy of the admin report is dumped to the pipeline as text
###	DontSendEmail  - Email is never sent to either users or admin
###

Param(
	[int]$daysForEmail = 14,
	[string]$adminEmail = &amp;quot;michael@smithcons.com&amp;quot;,
	[switch]$adminEmailOnly,
	[string]$SMTPfrom,
	[string]$SMTPserver,
	[string]$SMTPuser,
	[string]$SMTPpassword,
	[string]$anr,
	[switch]$SMTPuseSSL,
	[switch]$Quiet,
	[switch]$DontSendEmail
)

### Using Set-StrictMode helps protect against wonky errors that get caught by the
### compiler in compiled languages. Specifically (from the helpfile for the cmdlet):
### -- Prohibits references to uninitialized variables (including uninitialized 
###    variables in strings).
### -- Prohibits references to non-existent properties of an object.
### -- Prohibits function calls that use the syntax for calling methods.
### -- Prohibits a variable without a name (${}).
###
### However, using strict mode means that extra care has to be taken when using
### hashtables and property value collections. You see that in this script every
### time you see the Item() accessor method being used.

Set-StrictMode -Version 2.0

### For information about ANR, see &amp;quot;Ambiguous Name Resolution&amp;quot; in
### http://technet.microsoft.com/en-us/library/cc978014.aspx

$domainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$domainName   = $domainObject.Name
$domainRoot   = &amp;quot;LDAP://&amp;quot; + $domainName
$domainADSI   = [ADSI]$domainRoot
$domainMode   = $domainADSI.&amp;#39;msDS-Behavior-Version&amp;#39; ## Windows2008Domain is 3.

### domainMode is a PITA. System.DirectoryServices.ActiveDirectory.Domain.DomainMode and
### Microsoft.ActiveDirectory.Management.ADDomainMode have different values for the same
### enums!
###
### The first type is returned by System.DirectoryServices, the second type by the
### Get-ADDomain PowerShell cmdlet.
###
### That is why I ignore both of those potential access methods and use ADSI to access the
### value directly from the domain object. For specific information about those values, see:
### http://msdn.microsoft.com/en-us/library/cc223742(v=prot.10).aspx

[System.Int64]$Script:MaxPasswordAge = 0

function GetMaximumPasswordAge
{
	###
	### GetMaximumPasswordAge
	###
	### Retrieve the maximum password age that is set on the domain object. This is
	### normally set by the &amp;quot;Default Domain Policy&amp;quot;.
	###

	if( $Script:MaxPasswordAge )
	{
		### Cache the value so that it only has to be retrieved once, converted
		### to an int64 once, and converted to days once. Win-win-win.

		return $Script:MaxPasswordAge
	}

	### Dealing with ADSI unfortunately also means dealing with COM objects.
	### Using ConvertLargeIntegerToInt64 takes the COM object and converts 
	### it into a native .Net type.

	[System.Int64]$Script:MaxPasswordAge = $domainADSI.ConvertLargeIntegerToInt64( $domainADSI.maxPwdAge.Value )

	### Convert to days
	### 	there are 86,400 seconds per day (24 * 60 * 60)
	### 	there are 10,000,000 nanoseconds per second

	[System.Int64]$Script:MaxPasswordAge = ( -$Script:MaxPasswordAge / ( 86400 * 10000000 ) )

	return [System.Int64]$Script:MaxPasswordAge
}

function newSecurePassword( [string]$password )
{
	###
	### newSecurePassword
	###
	### Take the normal string password provided and turn it into a 
	### secure string that can be used to set credentials.
	###
	### In PowerShell v2.0, this can be done with ConvertTo-SecureString.
	### That cmdlet isn&amp;#39;t available in v1.0 though.
	###

	$secure = New-Object System.Security.SecureString

	$password.ToCharArray() |% { $secure.AppendChar( $_ ) }

	return $secure
}

function newPSCredential( [string]$username, [string]$password )
{
	###
	### newPSCredential
	###
	### Create a new PSCredential object containing the provided
	### username and plain-text password.
	###

	$pass = newSecurePassword $password

	$cred = New-Object System.Management.Automation.PSCredential( $username, $pass )
	$pass = $null

	return $cred
}

###
### We need to find all those user&amp;#39;s who:
###	Are normal users                       0x00000200 ADS_UF_NORMAL_ACCOUNT
###	Are not disabled                       0x00000002 ADS_UF_ACCOUNTDISABLE
###	Do not have &amp;quot;password never expires&amp;quot;   0x00010000 ADS_UF_DONT_EXPIRE_PASSWD
###	Do not have &amp;quot;no password required&amp;quot;     0x00000020 ADS_UF_PASSWD_NOTREQD
###
###	Once we have the users, determine whether the user has a PSO
###	by examining msDS-PSOApplied (if the domain mode of the executing domain
###	is at Windows2008Domain or higher).
###
###	If the user has a PSO, load the PSO (and store it to a hashtable)
###	and evaluate the user&amp;#39;s password against the PSO.
###
###	If the user does not have a PSO, evalute the user&amp;#39;s password
###	against the Default Domain Policy Maximum Password Age (which
###	is found by the function GetMaximumPasswordAge above).
###
### The most efficient way to find users is to do an LDAP search. But building the
### proper search isn&amp;#39;t that easy. We need a number of filters:
###
###	objectCategory=Person
###	(userAccountControl &amp;amp; ADS_UF_NORMAL_ACCOUNT)     &amp;lt;&amp;gt; 0
###	(userAccountControl &amp;amp; ADS_UF_ACCOUNTDISABLE)     == 0
###	(userAccountControl &amp;amp; ADS_UF_DONT_EXPIRE_PASSWD) == 0
###	(userAccountControl &amp;amp; ADS_UF_PASSWD_NOTREQD)     == 0
###
### Since userAccountControl is a BIT-FLAG attribute (meaning that individuals bits
### of the value control these options) then we need to be able to do a bit-wise
### LDAP search. It&amp;#39;s important to realize that with a bit-wise search, the result
### of a particular filter is either not-one (!1, which is false) or not-zero (!0,
### which is true).
###
### So the next step in building our query is to use the LDAP bit-wise AND filter:
###
###	1.2.840.113556.1.4.803
###
### This is used to do a bit-wise AND of the attribute on the left to the value
### on the right. For example:
###
###	attribute:1.2.840.113556.1.4.803:=1024
###
### This means a bit-wise AND is done of the value of the attribute to the value
### 1024 (which is 0x400 in hexadecimal). If the result of that bit-wise AND is
### zero, then the value of the filter is false. If the result is non-zero, then
### the value of the filter is true. Putting a &amp;quot;!&amp;quot; in front of a false result
### makes the result true. Putting a &amp;quot;!&amp;quot; in front of a true result, makes it false.
###
### A combination of these two techniques makes it possible to scan for zero and
### non-zero bits (that is, those which are set to one and those which are set to
### zero).
###
### LDAP also supports a bit-wise OR filter, using the special value of:
###
###	1.2.840.113556.1.4.804
###
### Given the presence of AND and OR filters, it is possible to build very complex
### combination filters.
###
### A combination filter is built up of individual filters combined with either a
### logical OR (&amp;quot;|&amp;quot;) or a logical AND (&amp;quot;&amp;amp;&amp;quot;) and surrounded by parentheses.
###
###	(&amp;amp;
###		(objectCategory=Person)
###		(userAccountControl:1.2.840.113556.1.4.803:=512)
###		(!userAccountControl:1.2.840.113556.1.4.803:=2)
###		(!userAccountControl:1.2.840.113556.1.4.803:=65536)
###		(!userAccountControl:1.2.840.113556.1.4.803:=32)
###	)
###
### So, in pseudo-C code this is:
###
###	if( ( objectCategory == Person ) AND
###	    ( ( userAccountControl &amp;amp; ADS_UF_NORMAL_ACCOUNT     ) != 0 ) AND
###	    ( ( userAccountControl &amp;amp; ADS_UF_ACCOUNTDISABLE     ) == 0 ) AND
###	    ( ( userAccountControl &amp;amp; ADS_UF_DONT_EXPIRE_PASSWD ) == 0 ) AND
###	    ( ( userAccountControl &amp;amp; ADS_UF_PASSWD_NOTREQD     ) == 0 ) )
###	{
###		### we&amp;#39;ve got a matching user!
###	}
###
$ldapFilter =   &amp;quot;(&amp;amp;&amp;quot;							      +
			&amp;quot;(objectCategory=Person)&amp;quot;                             +
			&amp;quot;(userAccountControl:1.2.840.113556.1.4.803:=512)&amp;quot;    +
			&amp;quot;(!userAccountControl:1.2.840.113556.1.4.803:=2)&amp;quot;     +
			&amp;quot;(!userAccountControl:1.2.840.113556.1.4.803:=65536)&amp;quot; +
			&amp;quot;(!userAccountControl:1.2.840.113556.1.4.803:=32)&amp;quot;

if( $anr )
{
	###
	### using an ANR subquery allows us to reduce the result set from the LDAP query
	###
	$ldapFilter += &amp;quot;(anr=$anr)&amp;quot;
}

$ldapFilter += &amp;quot;)&amp;quot;

###
### build the LDAP search
###

$directorySearcher = New-Object System.DirectoryServices.DirectorySearcher
$directorySearcher.PageSize    = 1000
$directorySearcher.SearchRoot  = $domainRoot
$directorySearcher.SearchScope = &amp;quot;subtree&amp;quot;
$directorySearcher.Filter      = $ldapFilter

###
### load the properties we want
###

$directorySearcher.PropertiesToLoad.Add( &amp;quot;displayName&amp;quot;        ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( &amp;quot;mail&amp;quot;               ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( &amp;quot;pwdLastSet&amp;quot;         ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( &amp;quot;sAMAccountName&amp;quot;     ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( &amp;quot;userAccountControl&amp;quot; ) | Out-Null

if( $domainMode -ge 3 )
{
	### this attribute is only valid on Windows2008Domain and above
	$directorySearcher.PropertiesToLoad.Add( &amp;quot;msDS-PSOApplied&amp;quot; ) | Out-Null
}

$users = $directorySearcher.FindAll()

###
### All the data is in $users (or will be paged into $users).
### Build the necessary reports and emails.
###

$crnl = &amp;quot;`r`n&amp;quot;
$script:adminReport = &amp;quot;&amp;quot;

function line
{
	foreach( $arg in $args )
	{
		$script:adminReport += $arg + $crnl
	}
}

$now = Get-Date
$maximumPasswordAge = GetMaximumPasswordAge

line ( &amp;quot;Admin Report - Send Mail to Users with Expiring Passwords - Run Date/Time: &amp;quot; + $now.ToString() )
line &amp;quot; &amp;quot;
line &amp;quot;Parameters:&amp;quot;
line &amp;quot;        Days warning for sending email: $daysForEmail&amp;quot;
line &amp;quot;        Administrator email: $adminEmail&amp;quot;

if( $DontSendEmail )
{
	line &amp;quot;        Email will not be sent to either the administrator email or to user&amp;#39;s email&amp;quot;
}
elseif( $adminEmailOnly )
{
	line &amp;quot;        Only the administrator will be sent email&amp;quot;
}

if( ![System.String]::IsNullOrEmpty( $SMTPfrom ) )
{
	line &amp;quot;        SMTP From address: $SMTPfrom&amp;quot;
}

if( ![System.String]::IsNullOrEmpty( $SMTPserver ) )
{
	line &amp;quot;        SMTP server: $SMTPserver&amp;quot;
}

if( ![System.String]::IsNullOrEmpty( $SMTPuser ) )
{
	line &amp;quot;        SMTP user: $SMTPuser&amp;quot;
}

if( ![System.String]::IsNullOrEmpty( $SMTPpassword ) )
{
	line &amp;quot;        SMTP password: $SMTPpassword&amp;quot;
}

if( $SMTPuseSSL )
{
	line &amp;quot;        UseSSL with SMTP is set&amp;quot;
}

line &amp;quot;        Maximum Password Age in Default Domain Policy = $maximumPasswordAge&amp;quot;
line &amp;quot;        Domain Mode $($domainObject.DomainMode.ToString())&amp;quot;
line ( &amp;quot;        User count = &amp;quot; + $users.Count.ToString() )

if( [System.String]::IsNullOrEmpty( $SMTPserver ) -and [System.String]::IsNullOrEmpty( $PSEmailServer ) -and !$DontSendEmail )
{
	Write-Error &amp;quot;No email server was specified via the SMTPserver parameter or the `$PSEmailServer environment variable.&amp;quot;
	exit
}

if( ![System.String]::IsNullOrEmpty( $SMTPuser ) -and [System.String]::IsNullOrEmpty( $SMTPpassword ) )
{
	Write-Error &amp;quot;No SMTPpassword was specified.&amp;quot;
	exit
}

if( ![System.String]::IsNullOrEmpty( $SMTPpassword ) -and [System.String]::IsNullOrEmpty( $SMTPuser ) )
{
	Write-Error &amp;quot;No SMTPuser was specified.&amp;quot;
	exit
}

if( [System.String]::IsNullOrEmpty( $SMTPfrom ) )
{
	$SMTPfrom = &amp;quot;Password Administrator &amp;quot;
}

###
### some of the bitflags attached to the userAccountControl attribute
###

$ADS_UF_NORMAL_ACCOUNT     = 0x00200
$ADS_UF_ACCOUNTDISABLE     = 0x00002
$ADS_UF_DONT_EXPIRE_PASSWD = 0x10000
$ADS_UF_PASSWD_NOTREQD     = 0x00020

$psoCache = @{}

foreach( $user in $users )
{
	###
	### we spend some time being pretty careful dealing with the properties. this
	### allows us to verify we get the attributes we want, and for those that were
	### not present, we can establish reasonable defaults.
	###

	line &amp;quot; &amp;quot;

	$propertyBag = $user.properties
	if( !$propertybag )
	{
		line &amp;quot;error! null propertybag!&amp;quot;
		continue
	}

	$mail = &amp;quot;&amp;quot;
	$disp = &amp;quot;&amp;quot;
	$sam  = &amp;quot;&amp;quot;
	$pls  = 0
	$pso  = 0
	$uac  = 0
	$pwdX = $null

	$dispObj = $propertyBag.Item( &amp;#39;displayname&amp;#39; )
	if( $dispObj -and ( $dispObj.Count -gt 0 ) )
	{
		$disp = $dispObj.Item( 0 )
		if( !$disp ) { $disp = &amp;quot;&amp;quot; }
	}
	$dispObj = $null
	### line &amp;quot;displayname = $disp&amp;quot;

	$samObj = $propertyBag.Item( &amp;#39;samaccountname&amp;#39; )
	if( $samObj -and ( $samObj.Count -gt 0 ) )
	{
		$sam = $samObj.Item( 0 )
		if( !$sam ) { $sam = &amp;quot;&amp;quot; }
	}
	else
	{
		$sam = &amp;quot;&amp;quot;
	}
	$samObj = $null
	### line &amp;quot;sam = $sam&amp;quot;

	$uacObj = $propertyBag.Item( &amp;#39;useraccountcontrol&amp;#39; )
	if( $uacObj -and ( $uacObj.Count -gt 0 ) )
	{
		$uac = $uacObj.Item( 0 )
	}
	else
	{
		line &amp;quot;no uac for $sam, assumed 0x200&amp;quot;
		$uac = $ADS_UF_NORMAL_ACCOUNT
	}
	$uacObj = $null
	### line &amp;quot;uac = $uac&amp;quot;

	$plsObj = $propertyBag.Item( &amp;#39;pwdlastset&amp;#39; )
	if( $plsObj -and ( $plsObj.Count -gt 0 ) )
	{
		$pls = $plsObj.Item( 0 )
	}
	else
	{
		### this can be a normal occurence if the password has never been set
		$pls = 0
	}
	$plsObj = $null
	### line &amp;quot;pls = $pls&amp;quot;

	line ( $sam.PadRight( 21 ) + $pls.ToString().PadRight( 21 ) + $uac.ToString().PadRight( 8 ) + &amp;#39;0x&amp;#39; + $uac.ToString(&amp;#39;x&amp;#39;).PadRight( 8 ) )

	if( $pls -eq 0 )
	{
		line ( &amp;quot; &amp;quot; * 8 + &amp;quot;The password has never been set for this user, skipped&amp;quot; )
		continue
	}

	[System.Int64]$localMaxAge = $maximumPasswordAge

	if( $domainMode -ge 3 )
	{
		###
		### PSOs can be created on Server 2003, but they don&amp;#39;t work properly until
		### the domain mode is Windows2008Domain or higher.
		###
		### So if we find a PSO and the domain mode is Windows2008Domain or higher,
		### we first determine whether we&amp;#39;ve seen this PSO before. If we have seen
		### the PSO before, then we&amp;#39;ve stored the msDS-MaximumPasswordAge value for
		### the PSO into a hash table for quick retrieval. If we have not seen the
		### PSO before, then we use ADSI to load the PSO and retrieve the value for
		### msDS-MaximumPasswordAge and then cache the value for future access. By
		### using a cache, we only have to access Active Directory to obtain values
		### once per PSO, leading to a significant performance improvement compared
		### to using the native cmdlets or S.DS.
		###

		$psoObj = $propertyBag.Item( &amp;#39;msds-psoapplied&amp;#39; )
		if( $psoObj -and ( $psoObj.Count -gt 0 ) )
		{
			$pso = $psoObj.Item( 0 )
			### line ( &amp;quot;PSO object/name&amp;quot; + $pso )

			if( $psoCache.Item( $pso ) )
			{
				[System.Int64]$localMaxAge = $psoCache.Item( $pso )
				### line ( &amp;quot; &amp;quot; * 8 + &amp;quot;Accessed PSO from cache = &amp;quot; + $pso )
			}
			else
			{
				$psoADSI = [ADSI]( &amp;quot;LDAP://&amp;quot; + $pso )
				$ageOBJ = $psoADSI.&amp;#39;msDS-MaximumPasswordAge&amp;#39;

				[System.Int64]$localMaxAge = $psoADSI.ConvertLargeIntegerToInt64( $ageOBJ.Value )

				### Convert to days
				### 	there are 86,400 seconds per day (24 * 60 * 60)
				### 	there are 10,000,000 nanoseconds per second
				$localMaxAge = ( -$localMaxAge / ( 86400 * 10000000 ) )

				$psoCache.$pso = $localMaxAge
				### line ( &amp;quot; &amp;quot; * 8 + &amp;quot;Stored PSO to cache = &amp;quot; + $pso )

				$ageOBJ  = $null
				$psoADSI = $null

				### line &amp;quot;localMaxAge = $localMaxAge&amp;quot;
			}
		}
		else
		{
			### completely normal to not have a PSO, in that case, use the maxPwdAge
			### from the default domain policy.
			$pso = $null
			### line &amp;quot;pso is null&amp;quot;
		}
		$psoObj = $null
	}

	### In an Exchange environment, the &amp;#39;mail&amp;#39; attribute contains the primary SMTP
	### address for a user. In a non-Exchange environment, that should also be true,
	### but no system process validates it. We do presume that the mail address is
	### valid, if present.

	$mailObj = $propertyBag.Item( &amp;#39;mail&amp;#39; )
	if( $mailObj -and ( $mailObj.Count -gt 0 ) )
	{
		$mail = $mailObj.Item( 0 )
	}
	else
	{
		$mail = &amp;#39;&amp;#39;
	}

	if( 0 )
	{
		### The conditions reported on below cannot occur based on our LDAP filter
		### But they helped me develop and test the LDAP filter. :-)

		if( $uac -band $ADS_UF_NORMAL_ACCOUNT )
		{
			&amp;quot;`t`tNormal account&amp;quot;
		}
		else
		{
			&amp;quot;`t`tNot a normal account&amp;quot;
		}
		if( $uac -band $ADS_UF_ACCOUNTDISABLE )
		{
			&amp;quot;`t`tAccount disabled&amp;quot;
		}
		else
		{
			&amp;quot;`t`tAccount enabled&amp;quot;
		}
		if( $uac -band $ADS_UF_DONT_EXPIRE_PASSWD )
		{
			&amp;quot;`t`tPassword doesn&amp;#39;t expire&amp;quot;
		}
		else
		{
			&amp;quot;`t`tPassword expires&amp;quot;
		}
		if( $uac -band $ADS_UF_PASSWD_NOTREQD )
		{
			&amp;quot;`t`tPassword not required&amp;quot;
		}
		else
		{
			&amp;quot;`t`tPassword required&amp;quot;
		}
	}

	### If we get here, $pls is non-zero.
	###
	### $pls comes to us in FileTime format (the number of 100 nansecond ticks 
	### since Jan 1, 1601). So it must be converted to DateTime and adjusted 
	### for the normal clock in order for us to do our arithmetic on it. For
	### more information about FileTime, see:
	### msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx

	$date = [DateTime]$pls
	$passwordLastSet = $date.AddYears( 1600 ).ToLocalTime()
	$passwordExpires = $passwordLastSet.AddDays( $localMaxAge )

	line ( &amp;quot; &amp;quot; * 8 + &amp;quot;The password was last set on &amp;quot; + $passwordLastSet.ToString() )

	if( $now -gt $passwordExpires )
	{
		line ( &amp;quot; &amp;quot; * 8 + &amp;quot;The password already expired on $($passwordExpires.ToString()). No email will be sent.&amp;quot;)
		continue
	}
	else
	{
		line ( &amp;quot; &amp;quot; * 8 + &amp;quot;The password will expire on &amp;quot;  + $passwordExpires.ToString() )
	}

	$difference = $passwordExpires - $now
 	$days       = $difference.Days.ToString()
	$hours      = $difference.Hours.ToString()
	$minutes    = $difference.Minutes.ToString()

	line ( &amp;quot; &amp;quot; * 8 + &amp;quot;(This is in $days days, $hours hours, $minutes minutes)&amp;quot; )

	if( $difference.Days -le $daysForEmail )
	{
		if( [System.String]::IsNullOrEmpty( $mail ) -and !$adminEmailOnly )
		{
			line ( &amp;quot; &amp;quot; * 8 + &amp;quot;Oops - user doesn&amp;#39;t have an email address.&amp;quot; )
			continue
		}

		if( $DontSendEmail )
		{
			line ( &amp;quot; &amp;quot; * 8 + &amp;quot;Oops - we aren&amp;#39;t supposed to send an email.&amp;quot; )
			continue
		}

		line ( &amp;quot; &amp;quot; * 8 + &amp;quot;This user will be sent an email for password change.&amp;quot; )

		$hash = @{}
		$hash.To       = $adminEmail
		$hash.Priority = &amp;#39;High&amp;#39;
		$hash.From     = $SMTPfrom

		if( ![System.String]::IsNullOrEmpty( $disp ) )
		{
			$hash.Subject = &amp;quot;Warning! The network password for $disp ($sam) is about to expire.&amp;quot;
		}
		else
		{
			$hash.Subject  = &amp;quot;Warning! The network password for $sam is about to expire.&amp;quot;
		}

		if( !$adminEmailOnly -and ![System.String]::IsNullOrEmpty( $mail ) )
		{
			$hash.CC = $mail
		}

		if( ![System.String]::IsNullOrEmpty( $SMTPserver ) )
		{
			$hash.SmtpServer = $SMTPserver
		}
		elseif( ![System.String]::IsNullOrEmpty( $PSEmailServer ) )
		{
			$hash.SmtpServer = $PSEmailServer
		}

		###
		### Send-MailMessage will default to using $PSEmailServer when no other SMTP server is specified.
		### We checked earlier to ensure that at least one of those was specified.
		###

		if( ![System.String]::IsNullOrEmpty( $SMTPuser ) )
		{
			###
			### If SMTPuser is specified then SMTPpassword is also specified.
			### We checked earlier to make certain that if one was specified,
			### then both were specified.
			###

			$hash.Credential = newPSCredential $SMTPuser $SMTPpassword
		}

		if( $SMTPuseSSL )
		{
			$hash.UseSSL = $true
		}

		$bodyHeader = @&amp;quot;
WARNING!

&amp;quot;@
		$bodyHeader += &amp;quot;`r`nFor network user id: &amp;quot;
		if( ![System.String]::IsNullOrEmpty( $disp ) )
		{
			$bodyHeader += $disp + &amp;quot; (&amp;quot; + $sam + &amp;quot;)&amp;quot;
		}
		else
		{
			$bodyHeader += $sam
		}

		$hash.Body = @&amp;quot;
$bodyHeader

Your password is about to expire in $days days, $hours hours, $minutes minutes. 

Please change it now!

Thank you,
Your System Administrator
&amp;quot;@

		###
		### This is V2.0 function. I should replace it with something v1.0 compatible.
		### (splatting is also V2.0 only, which is why I&amp;#39;m lazy and didn&amp;#39;t replace it.)
		###

		Send-MailMessage @hash

		$hash = $null
	}
}

###
### Invidual report emails complete.
### Now send summaries to the console and/or to the administrator email address.
###

line &amp;quot; &amp;quot;

if( !$quiet )
{
	###
	### If $quiet is NOT set, then dump the report to the console, as well
	### as sending the email to the administrator.
	###
	$script:adminReport
}

if( !$DontSendEmail )
{
	$hash = @{}
	$hash.To       = $adminEmail
	$hash.Priority = &amp;#39;High&amp;#39;
	$hash.From     = $SMTPfrom
	$hash.Subject  = &amp;quot;Admin Report - Send Mail to Users with Expiring Passwords - Run Date/Time: &amp;quot; + $now.ToString()
	$hash.Body     = $adminReport

	###
	### Send-MailMessage will default to using $PSEmailServer when no other SMTP server is specified.
	### We checked earlier to ensure that at least one of those was specified.
	###

	if( ![System.String]::IsNullOrEmpty( $SMTPserver ) )
	{
		$hash.SmtpServer = $SMTPserver
	}
	elseif( ![System.String]::IsNullOrEmpty( $PSEmailServer ) )
	{
		$hash.SmtpServer = $PSEmailServer
	}

	if( ![System.String]::IsNullOrEmpty( $SMTPuser ) )
	{
		###
		### If SMTPuser is specified then SMTPpassword is also specified.
		### We checked earlier to make certain that if one was specified,
		### then both were specified.
		###

		$hash.Credential = newPSCredential $SMTPuser $SMTPpassword
	}

	if( $SMTPuseSSL )
	{
		$hash.UseSSL = $true
	}

	Send-MailMessage @hash

	$hash = $null
}

###
### Clean up a bit.
###

$now   = $null
$users = $null

$psoCache        = $null
$directorySearch = $null
$domainADSI      = $null
$domainObject    = $null

$script:adminreport = $null

### Done.&lt;/font&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;Until next time... &lt;/p&gt;
&lt;p&gt;If there are things you would like to see written about, please let me know. &lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6971" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Windows/default.aspx">Windows</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item><item><title>Installing Exchange 2010 Service Pack 2</title><link>http://theessentialexchange.com/blogs/michael/archive/2011/12/07/installing-exchange-2010-service-pack-2.aspx</link><pubDate>Wed, 07 Dec 2011 14:52:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6970</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;Exchange 2010 Service Pack 2 was released-to-web on Monday (December 5, 2011).&lt;/p&gt;
&lt;p&gt;Containing literally hundreds of code corrections since service pack 1, plus several significant new features, service pack 2 is an important new release. I will not attempt to cover all of its details, that has been done-to-death on other web sites and blogs, including Microsoft&amp;#39;s own. Instead, I&amp;#39;ll share one new &amp;quot;gotcha&amp;quot; and give you my opinion on best practices for installing the service pack.&lt;/p&gt;
&lt;p&gt;The &amp;quot;gotcha&amp;quot;? First, a little background: Address Book Policies (ABPs) are an important new feature contained within service pack 2. They depend on the Exchange 2010 architecture, where all client communication is routed through the Client Access Server role. However, one of the things happening behind the scenes in the Exchange 2010 architecture is that the CAS role also hosts the &amp;quot;NSPI protocol&amp;quot;. NSPI stands for Name Server Provider Interface, and it is the mechanism used by messaging clients to access and manipulate address data stored in Active Directory (paraphrased&amp;nbsp;from Microsoft&amp;#39;s NSPI Protocol document, available &lt;a title="NSPI Protocol Documentation" href="http://msdn.microsoft.com/en-us/library/dd942204(v=PROT.10).aspx" target="_blank"&gt;here&lt;/a&gt;). Historically, NSPI was hosted by group catalog servers (i.e., domain controllers), because NSPI information isn&amp;#39;t used by only Exchange.&lt;/p&gt;
&lt;p&gt;Here is the kicker: when you install Exchange on a domain controller, Exchange will use the NSPI provider from the domain controller and not install it&amp;#39;s own as part of the Client Access Server role. This means that Address Book Policies will not work on Exchange Servers that are also domain controllers. This means that ABPs do not work on any SBS servers,&amp;nbsp;or on&amp;nbsp;any other &amp;quot;kitchen-sink&amp;quot; servers that may exist. There is your &amp;quot;gotcha&amp;quot; - to use ABPs, you must run Exchange and domain controllers as separate servers.&lt;/p&gt;
&lt;p&gt;Now, on to installing Exchange 2010 Service Pack 2. As always, the role installation order is Client Access, Hub Transport, Unified Messaging, Mailbox. Edge Servers can be done in any order.&lt;/p&gt;
&lt;p&gt;In the best of all possible worlds, you simply double-click on setup.exe and you are done, right? In the best of all possible worlds, that may be true. But, few of us live in that special place. :-) So, there are some things that we can do to ensure that our service pack install is &amp;quot;smooth as silk&amp;quot;.&lt;/p&gt;
&lt;p&gt;My list is:&lt;/p&gt;
&lt;p&gt;For CAS, first install the new prequisite required by SP2. To do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;div&gt;Open an elevated PowerShell session&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;Import-Module ServerManger&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;Add-WindowsFeature Web-WMI&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;Exit&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;If you are running Database Availability Groups (DAGs) on Windows Server 2008 R2, and you have not already installed it, install the hotfix from &lt;a title="A transient communication failure causes a Windows Server 2008 R2 failover cluster to stop working" href="http://support.microsoft.com/kb/2550886" target="_blank"&gt;KB2550886 - A transient communication failure causes a Windows Server 2008 R2 failover cluster to stop working&lt;/a&gt;. For more information about this hotfix, see the Microsoft Exchange Team blog posting: &lt;a title="Recommended Windows Hotfix for Database Availability Groups running Windows Server 2008 R2" href="http://blogs.technet.com/b/exchange/archive/2011/11/20/recommended-windows-hotfix-for-database-availability-groups-running-windows-server-2008-r2.aspx" target="_blank"&gt;Recommended Windows Hotfix for Database Availability Groups running Windows Server 2008 R2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Stop all ForeFront services - this includes ForeFront EndPoint Protection (FEP, which is local anti-virus) and ForeFront Protection for Exchange (FPE, which is Exchange anti-virus and anti-spam).&lt;/p&gt;
&lt;p&gt;Stop all local anti-virus services.&lt;/p&gt;
&lt;p&gt;Stop&amp;nbsp;all System Center agents (this includes services named &amp;quot;MOM&amp;quot; and &amp;quot;System Center Management&amp;quot; and any other agent that may attempt to load PowerShell or the Exchange Management Shell or any Exchange snap-ins or modules).&lt;/p&gt;
&lt;p&gt;Stop the &amp;quot;Task Scheduler&amp;quot; service. This prevents any manually scheduled tasks, or those schedule by, for example, System Center Operations Manager, from firing up while you are installing a service pack a screwing it up.&lt;/p&gt;
&lt;p&gt;(Optional) Install the schema update manually. To do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;div&gt;Open an elevated PowerShell session, under an account which is an Enterprise Admin, Domain Admin, and Schema admin&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;setup.com /ps&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;exit&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;If you are using DAGs on the current server, then you need to mark the current server&amp;nbsp;as &amp;quot;offline - undergoing maintenance&amp;quot;. Exchange provides a script for that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;div&gt;Open the Exchange Management Shell (EMS)&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;cd $exscripts&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;.\StartDagServerMaintenance -ServerName ExDag01&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;exit&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Note: you must substitute the proper server name for ExDag01&lt;/p&gt;
&lt;p&gt;Almost there! Start Task&amp;nbsp;Manager, check the box for &amp;quot;Show processes for all users&amp;quot;, and sort by Image Name. Verify that no instances of PowerShell.exe or MMC.exe are running. If there are, figure out why and get them closed.&lt;/p&gt;
&lt;p&gt;Finally, apply the service pack.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;From a cmd.exe&amp;nbsp;prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;font face="courier new,courier"&gt;setup.com /m:upgrade&lt;/font&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;From setup.exe, simply double-click the setup.exe executable and follow the bouncing ball.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Next, I recommend a reboot of the server. Just because I&amp;#39;m old school.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;If you are using DAGs on the current server, then you need to mark the current server as &amp;quot;back online&amp;nbsp;- ready for use&amp;quot;. Exchange provides a script for that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;div&gt;Open the Exchange Management Shell (EMS)&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;cd $exscripts&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;.\StopDagServerMaintenance -ServerName ExDag01&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;font face="courier new,courier"&gt;exit&lt;/font&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Note: you must substitute the proper server name for ExDag01&lt;/p&gt;
&lt;p&gt;That&amp;#39;s it!&lt;/p&gt;
&lt;p&gt;If you follow this process, you will have a very high likelihood of your service pack installation going smoothly and without challenges. Obviously, for any given customer environment, this process can be automated. Doing so for a generic environment would be quite challenging. If I learn of any other service pack challenges that can be mitigated, I&amp;#39;ll either update this post or create a new one and link to this one.&lt;/p&gt;
&lt;p&gt;Until next time... &lt;/p&gt;
&lt;p&gt;If there are things you would like to see written about, please let me know. &lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6970" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Upgrade_2F00_Migration/default.aspx">Upgrade/Migration</category></item><item><title>Creating Explicit Credentials in PowerShell for WMI, Exchange, Lync, Remoting, etc.</title><link>http://theessentialexchange.com/blogs/michael/archive/2011/11/07/creating-explicit-credentials-in-powershell-for-wmi-exchange-lync-remoting-etc.aspx</link><pubDate>Mon, 07 Nov 2011 15:58:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6969</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;When creating PowerShell cmdlets for any Microsoft technology - WMI, Exchange, Lync, etc. - it is common to need to provide credentials that are different from the default credentials. This can be even more important when you are using PowerShell remoting to connect to a remote computer.&lt;/p&gt;
&lt;p&gt;However, using the built-in cmdlet Get-Credential causes a dialog box to be opened on the console! (And it will simply fail in some cases, when the internal PowerShell $host.UI.PromptForCredential interface has not been implemented.) This is certainly not something that you want to happen when your PowerShell script is being called with remote PowerShell or from a service, or in many other scenarios.&lt;/p&gt;
&lt;p&gt;The solution is to pass in the full credential, already containing the secure password and the user names and (optionally) the domain or a user principal name. This is a bit challenging, as the constructor for a secure string doesn&amp;#39;t provide you an option for passing in an entire password. Therefore, you must build the secure string one character at a time.&lt;/p&gt;
&lt;p&gt;The two functions below make the process easy.&lt;/p&gt;
&lt;p&gt;Note: the $username parameter to newPSCredential can be in several formats: a plain username, a domain\username, or username@domain.com, or computername\username (for a local user).&lt;/p&gt;
&lt;p&gt;Note 2: some functions want a NetworkCredential instead of a PSCredential. Creating one of those is as simple as changing System.Management.Automation.PSCredential to System.Net.NetworkCredential.&lt;/p&gt;
&lt;p&gt;Note 3: as a security best practice, after you call the newPSCredential function, you should ensure that the plain text password is no longer available in the calling routine.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;font color="green"&gt;&lt;pre&gt;function newSecurePassword( [string]$password )
{
                ###
                ### newSecurePassword
                ###
                ### Take the normal string password provided and turn it into a 
                ### secure string that can be used to set credentials.
                ###

                $secure = new-object System.Security.SecureString

                $password.ToCharArray() |% { $secure.AppendChar( $_ ) }

                return $secure
}

function newPSCredential( [string]$username, [string]$password )
{
		###
		### newPSCredential
		###
		### Create a new PSCredential object containing the provided
		### username and plain-text password.
		###

                $pass = newSecurePassword $password

                $cred = new-object System.Management.Automation.PSCredential( $username, $pass )

                $cred
}

&lt;/pre&gt;&lt;/font&gt;
&lt;p&gt;Until next time... &lt;/p&gt;
&lt;p&gt;If there are things you would like to see written about, please let me know. &lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6969" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Lync/default.aspx">Lync</category></item><item><title>A Somewhat Detailed Look at Exchange 2010 Service Pack 2 Schema Changes</title><link>http://theessentialexchange.com/blogs/michael/archive/2011/11/06/a-somewhat-detailed-look-at-exchange-2010-service-pack-2-schema-changes.aspx</link><pubDate>Mon, 07 Nov 2011 03:32:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6968</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;At Exchange Connections 2011 in Las Vegas, Greg Taylor of Microsoft made a comment about some schema changes coming in Exchange 2010 Service Pack 2, that were enabling potential future feature content. This led to interest on my part to investigate those changes further.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s worthwhile to note that the Exchange Team at Microsoft allows schema updates to occur during service pack upgrades, but not during Update Rollups. This could potentially allow the Exchange Team to create schema changes that wouldn&amp;#39;t have any support in the cmdlets released for the service pack, but cmdlets could be modified and improved in Update Rollups.&lt;/p&gt;
&lt;p&gt;The Exchange Team has actually already released the schema changes that are coming in Exchange 2010 Service Pack 2. You can download a copy of these changes in the &lt;a title="Active Directory Schema Changes Reference, October 2011" href="http://www.microsoft.com/download/en/confirmation.aspx?id=5401" target="_blank"&gt;Exchange Server Active Directory Schema Changes Reference, October 2011&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The information contained in the document is very Active Directory oriented. So if you aren&amp;#39;t very familiar with the AD Schema you may find the information confusing.&lt;/p&gt;
&lt;p&gt;There are several very interesting items.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;div&gt;The Mail-Recipient class has now gained the Company and Department attributes.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;This means that Groups (both security groups and distribution groups) and Contacts (mail contacts) can now be assigned values to the Company and Department attributes. &lt;br /&gt;&lt;br /&gt;From a technical perspective, the Mail-Recipient class is a system auxiliary class, for both the Group and Contact classes, and all attributes present in Mail-Recipient are available in them.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;The ms-Exch-Custom-Attributes class has gained 35 new custom attributes, from ms-Exch-Extension-Attribute-16 to ms-Exch-Extension-Attribute-45, and ms-Exch-Extension-Custom-Attribute-1 through ms-Exch-Extension-Custom-Attribute-5.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;This means that Contacts, Groups, Users, Public Folders, Dynamic Distribution Lists, and Recipient Policies all now have a huge number of new attributes that can be assigned arbitrary values by an organization. This is welcome news to organizations who are using many or most of the current custom attributes and are wary to extend the schema themselves.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;From a technical perspective, the ms-Exch-Custom-Attributes class is an auxiliary class for all the named classes above.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;Many new attributes and classes were added to provide support for Address Book Policies and to enhance access to various address lists, global address lists, and offline address lists maintained by Exchange. &lt;br /&gt;&lt;br /&gt;The master class is ms-Exch-Address-Book-Mailbox-Policy.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;There are several new attributes and one new class (ms-Exch-Coexistence-Relationship) that are probably designed to support the Hybrid Coexistence Wizard and to overall simplify the process of configuring hybrid coexistence with Exchange Online.&lt;br /&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;There is a new class (ms-Exch-ActiveSync-Device-Autoblock-Threshold) and a number of new attributes that are within that class that appear to be designed to support automatic throttling of ActiveSync devices. However, that new potential feature was not discussed in Microsoft&amp;#39;s blog post on &lt;a title="Announcing Exchange 2010 Service Pack 2" href="http://blogs.technet.com/b/exchange/archive/2011/05/17/announcing-exchange-2010-service-pack-2.aspx" target="_blank"&gt;Announcing Exchange 2010 Service Pack 2&lt;/a&gt;.&lt;/div&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Along with the above changes, various MAPI IDs were added to provide access to the above attributes and classes from within MAPI applications, and new OIDs were assigned to each of the new attributes and new classes.&lt;/p&gt;
&lt;p&gt;While cmdlets may or may not appear in service pack 2 to manipulate all of these objects and attributes, they are welcome additions and will be available for organizations to utilize in custom programs.&lt;/p&gt;
&lt;p&gt;Until next time... &lt;/p&gt;
&lt;p&gt;If there are things you would like to see written about, please let me know. &lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6968" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Active+Directory/default.aspx">Active Directory</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Upgrade_2F00_Migration/default.aspx">Upgrade/Migration</category></item><item><title>Generating a report on Distribution Groups and their Membership, v2</title><link>http://theessentialexchange.com/blogs/michael/archive/2011/07/21/generating-a-report-on-distribution-groups-and-their-membership-v2.aspx</link><pubDate>Fri, 22 Jul 2011 02:57:00 GMT</pubDate><guid isPermaLink="false">57ea7d7e-b8c6-404f-99fb-04d07393726a:6967</guid><dc:creator>michael</dc:creator><slash:comments>0</slash:comments><description>&lt;p&gt;In&amp;nbsp;August of 2010, I posted &lt;a title="Generating a report on Distribution Groups and their Membership" href="http://theessentialexchange.com/blogs/michael/archive/2010/08/18/generating-a-report-on-distribution-groups-and-their-membership.aspx" target="_blank"&gt;Generating a report on Distribution Groups and their Membership&lt;/a&gt;. For most people, that script worked just fine.&lt;/p&gt;
&lt;p&gt;However, it had some issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large groups would cause PowerShell to generate an error about concurrent pipelines&lt;/li&gt;
&lt;li&gt;The script generated string output instead of object output&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This new version fixes those issues and adds &amp;#39;help&amp;#39; content for the script. The script is below, but the information about the reasoning is copied directly from the original post, modified appropriately.&lt;/p&gt;
&lt;p&gt;A common request is to get a list of all distribution groups and the members contained in that distribution group.&lt;/p&gt;
&lt;p&gt;Note: a distribution group may &lt;strong&gt;also&lt;/strong&gt; be a security group. From an Exchange Server perspective, the important thing is whether the group is mail-enabled or not.&lt;/p&gt;
&lt;p&gt;You can take this report and pipe it to out-file in order to save the output to a disk file. Then you can inspect the file later, email it, copy-n-paste it, whatever you want. Export-Csv and Export-CliXml are also good options for exporting this data, especially since there is the potential for the contents of several arrays to be output.&lt;/p&gt;
&lt;p&gt;Here is the script:&lt;/p&gt;
&lt;p&gt;&lt;font color="green"&gt;
&lt;blockquote&gt;&lt;pre&gt;##
## Report-DistributionGroupsAndMembers.ps1
## v2.0
##
## Michael B. Smith
## http://TheEssentialExchange.com
## August, 2010
## July, 2011
##
## Requires the Exchange Management Shell
## As of version 2.0, requires PowerShell 2.0
## Tested on both Exchange 2007 and Exchange 2010
##

#requires -Version 2.0

if( $args )
{
	if( ( $args.Length -eq 1 ) -and
	    ( ( $args[0] -eq &amp;quot;-?&amp;quot; ) -or
	      ( $args[0] -eq &amp;quot;-help&amp;quot; ) ) )
	{
		@&amp;quot;

NAME
	Report-DistributionGroupsAndMembers

SYNOPSIS
	This script outputs information about all distribution groups. The 
	information includes:

	GroupName    - The name of the distribution group
	Identity     - The unique identity of the group (an X400 identifier)
	ManagerNames - An array containing the names of the managers for the
		group. On Exchange 2007, this will contain a maximum of one
		entry. On Exchange 2010, there may be many entries. It is
		also possible (and quite likely in some environments), for
		this array to be empty.
	ManagerCanonicalNames - An array containing the canonical names
		(also known as the relative distinguished name) for the
		managers of this group. It will match, index by index, the
		contents of ManagerNames.
	Members      - An array containing the names of all the members of
		this group. It is also possible for this array to be empty.

	This script must be executed from within an Exchange Management Shell. 

SYNTAX
	Report-DistributionGroupsAndMembers [-help]

&amp;quot;@ 		| out-default

		return
	}

	throw &amp;quot;No parameters are allowed for this script&amp;quot;
}

$distributionGroups = Get-DistributionGroup -ResultSize Unlimited

foreach( $distributionGroup in $distributionGroups )
{
	## not all of these temporaries are necessary, but it
	## simplifies understanding the PowerShell code

	$groupName  = $distributionGroup.Name
	$groupID    = $distributionGroup.Identity

	$managedBy  = $distributionGroup.ManagedBy
	$mgrName    = @()
	$mgrCName   = @()

	if( $managedBy -is [Microsoft.Exchange.Data.Directory.ADObjectId] )
	{
		$mgrName  += $managedBy.Name
		$mgrCName += $managedBy.RDN.ToString().SubString(3)
	}
	elseif( $managedBy.Count -gt 0 )
	{
		foreach( $manager in $managedBy ) 
		{
			$mgrName  += $manager.Name
			$mgrCName += $manager.RDN.ToString().SubString(3)
		}
	}

	$membersArray = @()

	$members = Get-DistributionGroupMember -Identity $groupID -ResultSize Unlimited
	foreach( $member in $members )
	{
		$membersArray += $member.Name
	}

	$members = $null

	$hash = @{
		GroupName		= $groupName
		Identity		= $groupID
		ManagerNames		= $mgrName
		ManagerCanonicalNames	= $mgrCName
		Members			= $membersArray
	}

	New-Object PSObject -Property $hash	## inject to the pipeline
}

$distributionGroups = $null

&lt;/pre&gt;&lt;/blockquote&gt;&lt;/font&gt;
&lt;p&gt;Until next time... &lt;/p&gt;
&lt;p&gt;If there are things you would like to see written about, please let me know. &lt;/p&gt;&lt;img src="http://theessentialexchange.com/aggbug.aspx?PostID=6967" width="1" height="1"&gt;</description><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Administration/default.aspx">Administration</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Script/default.aspx">Script</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/Exchange/default.aspx">Exchange</category><category domain="http://theessentialexchange.com/blogs/michael/archive/tags/PowerShell/default.aspx">PowerShell</category></item></channel></rss>