Posts Tagged ‘Exchange Management Shell (EMS)’

One liners: Get All Exchange Users Who Are Configured for Forwarding

May 7th, 2013 4 comments

Exchange 2013 logo 128x128Due to some legal requirements, I had a needed to list all users who were configured in Exchange to forward elsewhere. This was to ensure that mail wasn’t automatically leaving the environment. A simple, single line in the shell is all that’s needed to give me what I need.

Open Exchange Management Shell, and enter this:

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress}

We can clean this up and make it a little more presentable using something like:

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress} | Select-Object Name, @{Expression={$_.ForwardingAddress};Label="Forwarded to"}, @{Expression={$_.DeliverToMailboxAndForward};Label="Mailbox & Forward"}

And the results are a small table that shows the user name, which object mail is being forwarded to, and whether the mailbox is configured to both store and forward:


This allowed me to take a look at those user accounts, and disable the forwarding, forcing the users to use their Exchange mailbox.

For a long list, or if you just want the info in a file, we can export the results to a .csv using Export-Csv. To do this, use:

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress -ne $null} | Select-Object Name, @{Expression={$_.ForwardingAddress};Label="Forwarded to"}, @{Expression={$_.DeliverToMailboxAndForward};Label="Mailbox & Forward"} | Export-Csv c:\forwardedusers.csv -NoTypeInformation

Finding a Domain Controller Within the Same AD Site via PowerShell

November 7th, 2012 1 comment

Powershell_logo-137x137In Exchange Management Shell and Lync Server Management Shell, you can target many cmdlets at specific domain controllers. This is crucial, especially in larger environments, if you need to make sure AD replication delays aren’t going to cause issues. An example is enabling a user for Lync using Enable-CsUser, then trying to use Set-CsUser or Grant-CsExternalAccessPolicy. The second will fail if it sends it to a different domain controller than the first, and replication hasn’t completed. So, the -DomainController switch can be used. Just send each command to the same DC, and even in rapid succession, you’ll succeed.

However, if you’re reusing your scripts or functions, especially in different environments, you have to find a valid DC in same AD site, put that into the script/function, and go. What a waste of time!

We can streamline the process with just a couple lines of code. First, we use Get-WMIObject to retrieve info on the local computer.

[object]$ComputerInfo = (Get-WMIobject -class "Win32_NTDomain" -namespace "root\CIMV2")

Next, we assign a variable, $ADSite, to the site name returned from the first line

[string]$ADSite = $ComputerInfo[1].ClientSiteName

Then we get a list of DCs in that same site

$DCsInSite = (Get-ADDomainController -Filter {Site -eq "$ADSite"})

And lastly, we randomly pick a DC from that list

[string]$QueryDC = ($DCsInSite | Get-Random).name

$QueryDC can now be used in your code, such as

Enable-CsUser [user] -RegistrarFQDN [fqdn] -SipAddressType [SIP address type] -DomainController $QueryDC

And that’s it. The only real requirement here is that the ActiveDirectory module be loaded, so that the Get-ADDomainController cmdlet works. This is easy:

Import-Module ActiveDirectory

In its entirety, here is the code:

Import-Module ActiveDirectory
[object]$ComputerInfo = (Get-WMIobject -class "Win32_NTDomain" -namespace "root\CIMV2") 
[string]$ADSite = $ComputerInfo[1].ClientSiteName
$DCsInSite = (Get-ADDomainController -Filter {Site -eq "$ADSite"}) 
[string]$QueryDC = ($DCsInSite | Get-Random).name


One liners: List All Users Who Have Send-As Access To Other Mailboxes

October 23rd, 2012 No comments

Exchange 2013 logo 128x128If you need to list all users who have Send-As access to other user’s mailboxes in Exchange, try this little one-liner from Exchange Management Shell:

Get-Mailbox -ResultSize unlimited | Get-ADPermission | Where-Object {$_.ExtendedRights -like "Send-As" -and $_.User -notlike "NT AUTHORITY\SELF" -and (! $_.Deny)} | Format-List Identity,User,AccessRights,IsInherited

This will show you the user who has the right and the mailbox they have rights to.

Send-As rights

Send-As rights. Click to enlarge.

Note that I use fl (Full List) instead of ft (Full Table) because the identity field can be quite long.

Programatically Add Heys and Values to edgetransport.exe.config for Exchange 2010

February 22nd, 2012 No comments

Recently, some testing on some new Exchange 2010 hub transport servers yielded some less than expected performance results. Processor utilization was much higher during sustained load testing of message throughput in some dedicated message journal sites.

A colleague worked to determine a solution, and came up with adding two keys and respective values to the EdgeTransport.exe.config file in [Exchange installation folder]\bin. This caused a substantial drop in processor utilization, but caused another problem – how to deploy this solution easily, in a repeatable fashion? We certainly don’t want to have to manually edit dozens of XML files across the production environment.

Our deployment method was entirely scripted, so I set out to find a way to incorporate the fix into the server provisioning scripts. Having not had to deal with editing XML files before, I did a fair amount of searching online, but had trouble with nearly everything I found. Obscure errors, and overly complex code had me just cobbling some things together until it worked. I finally came up with the New-AppSetting function below. It’s lean and mean, but it works.

function New-AppSetting {
	  Adds keys and values to the EdgeTransport.exe.config file for Exchange 2010 	

	  Adds user defined keys and values to the EdgeTransport.exe.config file for Exchange 2010 and restarts MSExchangeTransport service 

	Version      			: 1.0
	Rights Required			: Local admin on server
	    				: ExecutionPolicy of RemoteSigned or Unrestricted
	Exchange Version		: 2010 SP1 UR6
    	Author(s)    			: Pat Richard (
	Dedicated Post			:
	Disclaimer   			: You running this script means you won't blame me if this breaks your stuff.
	Info Stolen from 		:	

		New-AppSetting -key [key] -value [value]

		None. You cannot pipe objects to this script.

	#Requires -Version 2.0

	[cmdletBinding(SupportsShouldProcess = $true)]
		[parameter(Position = 0, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true, Mandatory = $true, HelpMessage = "No key specified")]
		[parameter(Position = 0, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true, Mandatory = $true, HelpMessage = "No value specified")]
	[string]$configfile = $env:ExchangeInstallPath+"bin\EdgeTransport.exe.config"
	if (Test-Path $ConfigFile){
		[xml]$xml = Get-Content $ConfigFile
		[string]$currentDate = (get-date).tostring("MM_dd_yyyy-hh_mm_s")
		[string]$backup = $configfile + "_$currentDate"
		Copy-Item $configfile $backup
		$new = $xml.CreateElement('add')
		$new.SetAttribute('key', $key)
		$new.SetAttribute('value', $value)
		$xml.configuration.appSettings.AppendChild($new) | Out-Null
	Restart-Service MSExchangeTransport
} # end function New-AppSetting

You call it via:

New-AppSetting -key [key name] -value [value]

such as

 New-AppSetting -key "RecipientThreadLimit" -value "20"

And it will add the key at the bottom of the list in EdgeTransport.exe.config and restart the transport service for the change to take effect. Prior to making the change, it creates a backup copy of EdgeTransport.exe.config for safe keeping.

One caveat – I didn’t have a lot of time to add some error checking or validation. The script does not check to see if the key is already present in the list (in our case, it’s not). So if you run the function multiple times with the same key name, you’ll end up with that key appearing multiple times in EdgeTransport.exe.config. I worked around this quickly in my script by using the following:

if ((Get-Content ($env:ExchangeInstallPath+"bin\edgetransport.exe.config")) -notmatch "RecipientThreadLimit"){
	New-AppSetting -key "RecipientThreadLimit" -value "20"

If I get some free cycles, I’ll streamline this a little more. But it works, and we’re able to continue deploying dozens of hub transport servers.

Function: New-LocalExchangeConnection – Ensure Your PowerShell Script is Connected to Exchange 2010

December 28th, 2011 11 comments


When writing scripts that execute commands on an Exchange server, for Exchange 2010, it’s important to ensure that you’re running within a session connected to an Exchange server, and that all Exchange cmdlets are available. In Exchange 2007, we could load the Exchange snapins. But 2010 doesn’t use snapins. Some people would say that you can simply load the modules, but loading the modules bypasses RBAC, and is thus, not recommended. Mike Pfeiffer wrote a great article Managing Exchange 2010 with Remote PowerShell that sheds some light. It’s worth a read.

A solution around this is to run a PowerShell script that comes built into Exchange 2010. This makes available the Connect-ExchangeServer cmdlet, which will connect via remote PowerShell to Exchange. We can specify a server, or let the -auto option connect to the best server via autodiscover. New-LocalExchangeConnection is a function I wrote to connect:

function New-LocalExchangeConnection	{ 
	[cmdletBinding(SupportsShouldProcess = $true)]
	Write-Verbose "Checking for Exchange Management Shell"
	$Sessions = Get-PSSession | Where-Object {$_.ConfigurationName -eq "Microsoft.Exchange"}
	if (!($Sessions)){
		if (Test-Path "$env:ExchangeInstallPath\bin\RemoteExchange.ps1"){
			Write-Verbose "Exchange Management Shell not found - Loading..."
			. "$env:ExchangeInstallPath\bin\RemoteExchange.ps1"
			Write-Verbose "Exchange Management Shell loaded"
			Write-Verbose "Connecting to Exchange server"
			Connect-ExchangeServer -auto
			if (Get-PSSession | Where-Object {$_.ConfigurationName -eq "Microsoft.Exchange"}){
				Write-Verbose "Connected to Exchange Server"
				Write-Host "An error has occurred" -ForegroundColor red
			Write-Warning "Exchange Management Shell is not available on this computer"
		Write-Verbose "Exchange Management Shell already loaded"
} # end function New-LocalExchangeConnection

Calling this within your script will make ensuring that your script is running with access to Exchange cmdlets much simpler.


I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.

Functions: Get-LocalAdminGroupMembership and Set-LocalAdminGroupMembership – Local Admin Group Membership on Remote Machines

December 22nd, 2011 No comments

PowerShell-logo-128x84While writing some PowerShell scripts to automate the installation of Exchange on over 100 servers, I needed to set and then verify that a group (in this case, “Exchange Trusted Subsystem”) was a member of the local admins group on some remote servers.

We start with Get-LocalAdminGroupMembership. This function merely checks the local admins group on a remote server to see if the group to be added is already a member. If it is, it returns $true, if not, $false. We need to pass it two variables: $ComputerName, and $Member. We don’t need to run this function. It’s called from the second function.

function Get-LocalAdminGroupMembership	{
		[Parameter(Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
		$ComputerName = ".",
		[Parameter(Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
	if($ComputerName -eq "."){$ComputerName = (get-WmiObject win32_computersystem).Name}
	$computer = [ADSI]("WinNT://" + $ComputerName + ",computer")
	$Group = $computer.psbase.children.find("Administrators")
	$members= $Group.psbase.invoke("Members") | % {$_.GetType().InvokeMember("Name", "GetProperty", $null, $_, $null)}
	if ($members -match $member){return $true}else{return $false}
} # end function Get-LocalAdminGroupMembership


The second function does all the heavy lifting.

function Set-LocalAdminGroupMembership {
		[Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
		[string]$ComputerName = ".",
		[Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
		[Parameter(Position=2, Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
		[string]$Domain = $env:USERDNSDOMAIN

		if (!(Get-LocalAdminGroupMembership -ComputerName "$ComputerName" -Member "$Member")){
			if($ComputerName -eq "."){$ComputerName = $env:ComputerName.ToUpper()}    

  			$adsi = [ADSI]"WinNT://$ComputerName/administrators,group"
	  		Write-Host "Not connected to a domain." -ForegroundColor "red"
		} else {
			Write-Host "`"$Account`" is already a local admin on $ComputerName" -ForegroundColor yellow
		Get-LocalAdminGroupMembership -ComputerComputer "$ComputerName" -Member "$Member"
	}# Process
} # end function Set-LocalAdminGroupMembership

We call Set-LocalAdminGroupMembership and pass it the same parameters, $ComputerName and $Member

Set-LocalAdminGroupMembership -ComputerName mycomputer -Member "Exchange Trusted Subsystem"

The function will add the group to the local admins group, and then do a Get-LocalAdminGroupMembership for that same group and dump the results to the screen.

Update Rollup 6 (UR6) for Exchange Server 2010 SP1 Released

October 28th, 2011 No comments

Microsoft has released the following update rollup for Exchange Server 2010:

  • Update Rollup 6 for Exchange Server 2010 SP1 (2608646)

If you’re running Exchange Server 2010 SP1, you need to apply Update Rollup 6 for Exchange 2010 to address the issues listed below.

Remember, you only need to download the latest update for the version of Exchange that you’re running.

Here is a list of the fixes included in update rollup 6:

  1. 2431609 An update is available that updates the message of a retention policy in OWA for Exchange Server 2010
  2. 2449266 EWS drops the TCP connection to the EWS client application without any error message in a Microsoft Exchange Server 2010 environment
  3. 2480474 A Users do not receive quota warning messages after applying SP1 for Exchange 2010
  4. 2514820 An incoming fax message is not delivered to the recipient in an Exchange Server 2010 SP1 environment
  5. 2521927 Disabling the Exchange ActiveSync Integration feature for OWA does not take effect in OWA Premium clients in an Exchange Server 2010 environment
  6. 2528854 The Microsoft Exchange Mailbox Replication service crashes on a computer that has Exchange Server 2010 SP1 installed
  7. 2535289 The Microsoft Exchange Information Store service crashes occasionally when you run an antivirus application on an Exchange Server 2010 Mailbox server
  8. 2536313 Slow message delivery and mailbox access for journaling mailboxes on an Exchange Server 2010 server
  9. 2544246 You receive a NRN of a meeting request 120 days later after the recipient accepted the request in an Exchange Server 2010 SP1 environment
  10. 2548246 The Microsoft Exchange Information Store service crashes occasionally when a folder view is corrupted on an Exchange Server 2010 mailbox server
  11. 2549183 “There are no objects to select” message when you try to use the EMC to specify a server to connect to in an Exchange Server 2010 SP1 environment
  12. 2549289 A RBAC role assignee can unexpectedly run the Add-MailboxPermission command or the Remove-MailboxPermission command on an Exchange Server 2010 server that is outside the role assignment scope
  13. 2555851 A mailbox does not appear in certain address lists after you run commands on the mailbox in an Exchange Server 2010 SP1 environment
  14. 2559814 A user cannot add or remove delegates from a mailbox by using Outlook in an Exchange Server 2010 environment
  15. 2561514 Exchange Server 2003 user cannot view the free/busy information of a user in a different federated organization
  16. 2563860 You cannot create a new mailbox database if you already have 1000 mailbox databases in an Exchange Server 2010 environment
  17. 2567409 Certain free/busy messages are not replicated from an Exchange Server 2010 server to an Exchange Server 2003 server
  18. 2571791 Retention policies are applied to Contact items unexpectedly in an Exchange Server 2010 environment
  19. 2572052 Certain properties of a recurring meeting request from external email accounts are missing in an Exchange Server 2010 SP1 environment
  20. 2575005 You cannot start the EMC or the EMS in an Exchange Server 2010 Service Pack 1 environment
  21. 2578631 Certain users cannot send email messages to a mail-enabled public folder in an Exchange Server 2010 environment
  22. 2579172 Items that are deleted or moved still appear in the original folder when you use Office Outlook in online mode to access an Exchange Server 2010 mailbox
  23. 2579671 No results returned when you use the ExpandGroup method in EWS to retrieve a list of members of a Dynamic Distribution Group in an Exchange Server 2010 environment
  24. 2582095 The SmtpMaxMessagesPerConnection property of a send connector is not replicated to the subscribed Edge Transport server in an Exchange Server 2010 environment
  25. 2600835 The RPC Client Access service crashes when you delete an attachment of an item by using Outlook in online mode in an Exchange Server 2010 SP1 environment
  26. 2601701 The memory usage of the MSExchangeRepl.exe process keeps increasing when you perform a VSS backup on Exchange Server 2010 databases
  27. 2616127 “0x80041606” error code when you use Outlook in online mode to search for a keyword against a mailbox in an Exchange Server 2010 environment
  28. 2617126 The Store.exe process crashes when you send an email message that has attachments in an Exchange Server 2010 SP1 environment
  29. 2627769 Some time zones in OWA are not synchronized with Windows in an Exchange Server 2010 environment

Download the rollup here.

Installation Notes:

If you haven’t installed Exchange Server yet, you can use the info at Quicker Exchange installs complete with service packs and rollups to save you some time.

Microsoft Update can’t detect rollups for Exchange 2010 servers that are members of a Database Availability Group (DAG). See the post Installing Exchange 2010 Rollups on DAG Servers for info, and a script, for installing update rollups.

Update Rollups should be applied to Internet facing Client Access Servers before being installed on non-Internet facing Client Access Servers.

If you’re installing the update rollup on Exchange servers that don’t have Internet access, see “Installing Exchange 2007 & 2010 rollups on servers that don’t have Internet access” for some additional steps.

Also, the installer and Add/Remove Programs text is only in English – even when being installed on non-English systems.

Note to Forefront users:

If you don’t disable Forefront before installing a rollup or service pack, and enable afterwards, you run the risk of Exchange related services not starting. You can disable Forefront by going to a command prompt and navigating to the Forefront directory and running FSCUtility /disable. To enable Forefront after installation of a UR or SP, run FSCUtility /enable.

Script: New-DirectoryUpdateReminder.ps1 – Prompt Users to Update Their Active Directory Information

October 3rd, 2011 5 comments

This script will look at all users in AD, and determine if they are missing key information such as office, address, title, and manager. If they are, it will send them an email requesting they update their information. It should be noted that this script is designed for environments that have a self-service solution in place for users to update their information. This can include Exchange 2010, where ECP allows the user to change many fields:

ECP options to change user info

ECP options to change user info

In some of the environments that I build, where Exchange 2010 isn’t an option, or other fields need to be changed, I install Directory Update, a small footprint solution for IIS. Directory Update is a PHENOMENAL solution that’s inexpensive, yet feature packed. It’s fully configurable and features drop downs, check boxes, and logic to ensure that users are inputting the correct information in the correct format. It also allows you to specify what fields the user can update. I highly recommend it. Other environments might use some home-grown solution, or even SharePoint. Either way, a self-service solution takes the burden off the Help Desk. A perfect example is when a manager leaves the organization. When their AD account is deleted, the users who had that person listed as their manager will automatically start getting reminders from this script since the field is now empty.

Many orgs don’t worry as much about some of these fields. However, when the information is current and correct, the data can be pulled for other purposes, such as workflow applications, org charts, phone lists, etc. Some orgs use transport rules to create disclaimer or signature phrases such as how to contact a user’s manager. All of these are perfect reasons for using this script.


Runs as a scheduled task, and will remind users daily until their information is complete.

Can be run in DEMO mode to see which users would receive an email.

Can be run in PREVIEW mode to receive the formatted message to see what it looks like before rolling it out in production.


Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

This script requires a receive connector that will accept mail. See Creating a receive connector to use for sending email from PowerShell.

Once the receive connector is created, copy the script from the .zip file below to your server.  Open the script in any true text editor, and set the various parameters. See the highlighted lines in the script below. Each should be configured for your environment.

[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify a company name.")]
[string]$Company = "Contoso Ltd",
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify an OWA URL")]
[string]$UpdateUrl = "",
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify the IP address of your email server")]
[string]$EmailServer = "",
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify a name and email address for the email 'from' field")]
[string]$EmailFrom = "Help Desk ",
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
[string]$HelpDeskPhone = "(586) 555-1010",
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
[string]$HelpDeskURL = "",
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
[string] $TranscriptFilename = $MyInvocation.MyCommand.Name + " " + (hostname)+ " {0:yyyy-MM-dd hh-mmtt}.log" -f (Get-Date),
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, Mandatory=$false, HelpMessage="This must be zero")]
[int]$UsersNotified = 0,
[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
[string] $ImagePath = "",

Save the script on your server.

Copy the images in the .zip file to the path you specified on line 102 above.

Run the script in demo mode to see a list of users that would receive the email messages:

.\New-DirectoryUpdateReminder.ps1 -demo
New-DirectoryUpdateReminder -demo

New-DirectoryUpdateReminder scheduled task

Test the script’s email functionality next by using preview mode. In preview mode, a single user will receive the email message. This will allow you to see what the users will see, as well as ensure that the formatting and wording is sufficient.

.\New-DirectoryUpdateReminder.ps1 -preview -previewuser [username]

After receiving and reviewing the message, adjust the HTML code as needed.

To configure the script to run as a scheduled task, run the script in install mode using

.\New-DirectoryUpdateReminder -install

This will create a Windows scheduled task that will run the script every day at 6:30am. Once the scheduled task is created, feel free to edit it to change the time.

Editing the scheduled task

That’s all it takes. Feel free to leave comments below, including any feature requests you’d like.


I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.


v1.7 – 01-27-2014 –

v1.5 – 09-02-2011 – – image files used in emails


See the changelog for this script which details all versions and their features.

[Redirect] New-PasswordReminder.ps1 v2.2 – Target Specific OUs, Better Password Policy Info, Code Tweaks

September 30th, 2011 35 comments

Here’s the latest version of the script. For information on previous builds, including installation instructions, please see New-PasswordReminder.ps1 v2.1 – updated to include better formatting, preview, and installer!

Issues resolved:

  1. added some missing ‘alt’ tags for some images in email HTML code
  2. added $HelpDeskURL variable in param block. That resolves the problem of some links that weren’t clickable (whoops!)
  3. updated links to point to new blog. This includes the one in the event log message.

#2 is the only one that you need to worry about. Line 166 defines a URL for your Help Desk:

[parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory=$false)]
[string]$HelpDeskURL =,

New features/additions

  1. added code to determine global minimum password length & format message accordingly
  2. added code to determine global password complexity requirements & format message accordingly
  3. added some parameter validation
  4. added ability to target a single OU, and its children
  5. updated the Send-MailMessage line based on user feedback

#1 and #2 are quite similar. The script now looks at the default domain password policy and retrieves the minimum password length and complexity requirements. It then uses this info to formulate the text for the email. In previous versions of the script, the HTML code had to be set manually.

#3 is just to make sure valid info is passed to some of the parameters.

#4. This was a reader request. To target a specific OU, set the OU in the param block. Look at line 185

[parameter(ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false, HelpMessage = "Please specify an Organizational Unit")]
[string] $ou

And set $ou to the full name of the OU, such as

[parameter(ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false, HelpMessage = "Please specify an Organizational Unit")]
[string] $ou = "ou=myusers,dc=contoso,dc=com"

The script will only check user accounts in that OU, and ALL CHILD OUs.

#5 was done to improve performance, but also get around an issue that was reported by a reader. Two issues resolved at once!

Download the new version at and keep those comments coming!

Exchange and Lync Session Videos From Tech·Ed 2011

September 29th, 2011 No comments