This past weekend, while working with a customer on a new proof-of-concept deployment of Exchange Hybrid and Microsoft Teams, we had a need to validate that no user accounts would be inadvertently updated during the upgrading of Exchange 2003 Recipient Policies to modern Exchange email address policies.
Background
Despite the customer having only Exchange 2016 servers in their environment, none of the policies had been upgraded from the legacy Ldap-filter syntax circa Exchange 2003.
It’s a long, long story. We can talk about it over beers sometime.
After using Bill Long’s trusted ConvertFrom-LdapFilter script to determine the correct syntax, it was time to go. However, the customer was unsure about the ramifications of running this to update policies (as their legacy EAPs hadn’t been touched in nearly 20 years).
The primary concerns?
- Would this trigger a re-evaluation of EAPs?
- Would this trigger an address book rebuild?
- If users had been modified outside the Exchange management interface, would email addresses get updated or removed for those users if the EmailAddressPolicyEnabled property is set to True?
While the answer to all of those is upgrading an EAP should not trigger an address book rebuild, it’s always better to have a backup than to not.
So, here’s a quick way to compare a before and after for email addresses.
Before Script
First, you need to capture the current state of things. While you could dump it to a CSV for easy reading, I prefer to have this type of data in XML (because it means fewer PSCustomObject types).
The customer built their EAPs based on unique values in Department, but you can use any attribute:
<# .SYNOPSIS Export core values before policy changes. .PARAMETER FilterableProperty Select any filterable property name. .PARAMETER FilterablePropertyValue Enter a string value to match. .PARAMETER Output Output file name. #> param ( [string]$FilterableProperty, [string]$FilterablePropertyValue, [string]$Output = "Recipients.xml" ) If ($FilterableProperty -and $FilterablePropertyValue ) { Get-Recipient -Filter "$FilterableProperty -eq '$FilterablePropertyValue'" -ResultSize Unlimited | Select Identity, DisplayName, Alias, SamAccountName, PrimarySmtpAddress, EmailAddresses, $FilterableProperty | Export-Clixml $Output -Force } Else { Get-Recipient -Resultsize Unlimited | Select Identity, DisplayName, Alias, SamAccountName, PrimarySmtpAddress, EmailAddresses | Export-Clixml $Output -Force }
The content will be saved in the file name specified in $Output
(or the -Output
parameter).
After Script
Once you’ve made changes that could potentially impact users, you can re-run an export and compare the differences.
<# .SYNOPSIS Export core values after policy changes. .PARAMETER FilterableProperty Select any filterable property name. .PARAMETER FilterablePropertyValue Enter a string value to match. .PARAMETER Output Output file name for differences. .PARAMETER Source Input file name of source XML (generated from BEFORE script). #> param( [string]$FilterableProperty, [string]$FilterablePropertyValue, [string]$Output = "Differences.csv", [string]$Source = "Recipients.xml" ) If ($FilterableProperty -and $FilterablePropertyValue) { $PostChange = Get-Recipient -Filter "$FilterableProperty -eq '$FilterablePropertyValue'" -Resultsize Unlimited | Select Identity, Alias, SamAccountName, PrimarySmtpAddress, EmailAddresses, EmailAddressPolicyEnabled } Else { $PostChange = Get-Recipient -Resultsize Unlimited | Select Identity, Alias, SamAccountName, PrimarySmtpAddress, EmailAddresses, EmailAddressPolicyEnabled } $Original = Import-Clixml $Source Foreach ($Recipient in $Original) { Foreach ($Address in $Recipient.EmailAddresses) { If ($Address.ProxyAddressString -ilike "smtp:*") { If ($PostChange.EmailAddresses.ProxyAddressString -match $Address.ProxyAddressString ) { #do nothing, object found } Else { "$($Address.ProxyAddressString) for $($Recipient.DisplayName) ($($Recipient.Alias)) not found" Add-Content -Path "$Output" "$($Address.ProxyAddressString),$($Recipient.Alias),$($Recipient.EmailAddressPolicyEnabled)" } } } }
Again, just like the before script, you can use the FilterableProperty and FilterablePropertyValue to scope your selection.
Ta-da!
The output file (specified in the -Output
parameter) will have a comma-separated list identifying any missing proxy address, alias, and the current value of the object’s EmailAddressPolicyEnabled
property.
Updates away!