It's a common practice to cleanup and remove unused accounts on your Exchange and Active Directory environment. But how often do you cleanup those distribution groups? Recently I received a request to assist in just such an operation, so I turned to my two friends: Google and PowerShell.
A quick search on Google found that there was already someone who had tried to tackle this exact problem, and with some measure of success. He was kind enough to share his efforts, and PowerShell scripts on his blog. Thanks Ivan!
The process is very simple. You are going to look through your messaging logs for the any use of the a distribution group. Once you have a list of groups that are being used, you compare it to the list of all distribution groups in AD. Any unused groups are hidden from the GAL for a period of time. At the end of that period they can be removed from AD.
As with any scripts published on the internet, it's caveat emptor. So I did my due diligence and actually parsed out the scripts he had published. Most of them ran as advertised, but there was one key portion that I found ran differently. The comparison of the lists in step 4 will not work as he has it scripted. That is because of how the Compare-Object function actually works. The syntax of the command is Compare-Object $file1 $file2. This produces a table with two columns. The first column is any items found in one list and not the other. The second column is a direction arrow (<= or =>) indicating which file the item in first column was found in. Here's an example:
File 1
--------------
Line 1
This is line two
Line three here
File 2
--------------
Line 1
This is 2
Line three here
If we run the comparison, we get a table like this
InputObject SideIndicator
This is line two <=
This is 2 =>
This indicator <= means that the line was found in file 1 only. This indicator => means that the line was found in file 2 only.
Compare-Object also has a few additional command switches that can be added. The one used in the command on Ivan's page is -SyncWindow. This number is how far up and down a list Compare-Object will look to find a matching item. By default the SyncWindow is set to the maximum value of an Int32 object. If you set it to 500, as shown in Ivan's script, then you'll only be looking up and down the list 500 items. That should be fine for a small result set, but in my experience the result set it going to far exceed 500 items. My recommendation is to leave it at the default setting.
The script also does not actually look at the comparison, which means that your result set is all unique lines from both lists. That's not really what we're looking for. If list A is all distribution groups, and list B is all active groups, then you want all items in list A that are not in list B (<=). Those are your inactive groups.
The scripts also assume you only have a single Hub Transport server. What to do when you have multiple servers? Multiple compares naturally.
So here is the complete script I have so far.
#Distribution List comparison
Get-DistributionGroup -Resultsize unlimited | Select-Object PrimarySMTPAddress | Sort-Object PrimarySMTPAddress | Export-CSV DL-ALL.csv -notype
Get-MessageTrackingLog -Server YourServerName -EventId Expand -ResultSize Unlimited | Sort-Object RelatedRecipientAddress | Group-Object RelatedRecipientAddress | Sort-Object Name | Select-Object @{label="PrimarySmtpAddress";expression={$_.Name}}, Count | Export-CSV DL-Active-1.csv -notype
Get-MessageTrackingLog -Server YourOtherServerName -EventId Expand -ResultSize Unlimited | Sort-Object RelatedRecipientAddress | Group-Object RelatedRecipientAddress | Sort-Object Name | Select-Object @{label="PrimarySmtpAddress";expression={$_.Name}}, Count | Export-CSV DL-Active-2.csv -notype
$DLall = import-csv -path .\DL-All.csv
$L1 = import-csv -Path .\DL-Active-1.csv
$L2 = import-csv -Path .\DL-Active-2.csv
$Half = Compare-Object $DLall $L1 -Property PrimarySmtpAddress | where{$_.SideIndicator -eq "<="} | Sort-Object PrimarySmtpAddress | Select-Object -Property PrimarySmtpAddress
Compare-Object $Half $L2 -Property PrimarySmtpAddress | where{$_.SideIndicator -eq "<="} | Sort-Object PrimarySmtpAddress | Select-Object -Property PrimarySmtpAddress | Export-CSV DL-Inactive.csv -notype
At the end you have a list of unused addresses. No we need to feed that back to AD to hide them from the GAL.
I'll cover that process in a future post. For now, time to increase your log retention!
Labels: AD, exchange 2010, powershell, Scripts