In the United States we celebrate Thanksgiving tomorrow.  No matter where you are in the world let us all give thanks for PowerShell.  If Windows were the pumpkin pie, then PowerShell would be the whipped cream on top.  If Active Directory were the turkey, then PowerShell would be the stuffing.  If Exchange were the mashed potatoes, then PowerShell would be the gravy.  Eating and scripting are complementary passions, so we give thanks for both.
This post is part four in the "PowerShell: SID Walker, Texas Ranger" series on documenting and remediating SID history in your AD forest. Go back and read over the previous articles in the series if you haven't had a chance yet. In today's post we will look at the final step of remediating SID history:  removing the SID history data from our migrated AD objects.  Cleaning up this stale data will greatly reduce the chance of token size issues for your users.

Background

In an earlier post I explained the process for cleaning up SID history, and the last step is actually removing the data from your environment.  This is the last step.  It is crucial that you have already remediated your ACLs, swapping old SIDs for new SIDs.  The preferred method is the ADMT, and then for NAS file shares you can use the function I provided in part 2Please note that removing SID history may cause significant impact to your users if you have not taken the time to migrate resource ACLs.  I highly recommend reviewing the ADMT Guide to make sure you have completed all migration steps up to this point.  You have been warned.  Proceed carefully.  As a backout plan make sure you have a good system state backup from two DCs per domain.  Using VBScript to clear SID history was painful. PowerShell achieves the same end with far fewer lines of code.

Get-SIDHistory | Remove-SIDHistory

I decided to divide this function into two parts.  I wanted a dynamic AD query to retrieve users and groups by specific criteria as a way to surgically target the SID history removal.  Then I wanted to pipe the entries returned into a dedicated function for doing the actual removal.  This allows us to craft the right AD query in a safe environment before pulling the trigger on SID history.

Get-SIDHistory

When removing SID history it is best to proceed in phases by department or business unit.  You know the drill.  Start with a few users in IT, then all of IT, then company departments beginning with the least critical business impact (ie. Grounds Keeping before Purchasing).  You must send notification to your users asking them to verify that all of their resource access and drive letters are still good after the change.
Using Get-ADObject to query by name or OU is pretty straightforward, so I won't go into much detail there.  The magic of this function, though, is the -DomainName parameter.  You can specify the FQDN of the former domain for which you wish to remove SID history.  Now that is cool!  We can easily target the SID history by domain, because the SID object has a property called AccountDomainSid. We don't even have to parse it out. When we get back the SID history entries, we can filter on this attribute to target a specific domain in the SID history.
In order for this to work you have to first use Export-DomainSIDs to create the DomainSIDs.CSV file that maps the domain names to the domain SIDs.  If you don't know the old domain name, then you can use the -DomainSID switch to remove SID history for any unnamed domain.  You can copy the old domain SID from the SIDHistoryReport.CSV file produced by the function Export-SIDMapping (included in the attached code).  You could also modify the DomainSIDs.CSV file by manually adding any other domain names and SIDs from your own documentation.
Another feature of the Get-SIDHistory query is that it handles the multi-value nature of the sIDHistory attribute.  We do this using the ExpandProperty parameter of Select-Object which I demonstrated here.  This allows us to identify and target specific SID history entries when a user has been migrated more than once.  The ExpandProperty parameter ensures that we get a result row for every SID history entry regardless of the count.
Here is an example without ExpandProperty:
Get-ADUser user1 -Property sIDHistory | Select-Object name, sIDHistory | Format-Table name, sIDHistory –AutoSize
name   sidhistory
----   ----------
user1  {S-1-5-21-3013500491-1380372588-2491230877-1122, S-1-5-21-179552...

Here is an example with ExpandProperty:
Get-ADUser user1 -Property sIDHistory | Select-Object name, sIDHistory -ExpandProperty sidHistory | Format-Table name, sIDHistory –AutoSize
name   Value
----   -----
user1  S-1-5-21-3013500491-1380372588-2491230877-1122
user1  S-1-5-21-1795525639-2355993942-847153066-1695

Notice that ExpandProperty modifies the property name to "value".  We can rename that at the end of the pipe using Select-Object:
Select-Object DistinguishedName, @{name="SID";expression={$_.Value}}
Get-SIDHistory takes the parameters you specify, shapes them into an AD query, and then uses Invoke-Expression to run it.  In other words, this is code that writes code.  I love that feature of PowerShell.  The query parameters are cumulative.  Therefore you can pile on two or three criteria for laser accuracy.
Here are a few examples of how you can use Get-SIDHistory:
  • Get-SIDHistory –SamAccountName ashleym
  • Get-SIDHistory –DomainName wingtiptoys.com
  • Get-SIDHistory –DomainName wingtiptoys.com –SamAccountName ashleym
  • Get-SIDHistory –DomainSID S-1-5-21-2371126157-4032412735-3953120161
  • Get-SIDHistory –ObjectClass group
  • Get-SIDHistory –SearchBase "OU=Sales,DC=contoso,DC=com"
  • Get-SIDHistory –MemberOf MigratedUsers
  • Get-SIDHistory | Measure-Object
Experiment with different combinations of parameters until you isolate exactly the targets you need.  Then you can script out multiple statements for each phase of the remediation.

Remove-SIDHistory

This short TechNet article provides syntax for removing SID history:
Get-ADUser –filter 'sidhistory –like "*"' –searchbase "dc=name,dc=name" –searchscope subtree –properties sidhistory | foreach {Set-ADUser $_ -remove @{sidhistory=$_.sidhistory.value}}
That single line of PowerShell is a "resumé-generating-event".  DO NOT RUN THIS LINE IN YOUR ENVIRONMENT IF YOU WANT TO KEEP YOUR JOB.  Wiping SID history for every user all at once is not a best practice (in case you were wondering).
You can call Remove-SIDHistory by specifying the distinguishedName of the object and the SID entry to remove.  However, that is a lot of complicated typing.  The intention is to use Get-SIDHistory to tailor your query to exactly the results you want, and then simply pipe it to Remove-SIDHistory.  This works through a feature of PowerShell advanced functions called cmdlet binding.  The ValueFromPipelineByPropertyName argument allows us to pipe the output from one function into the next as long as the first output properties match the second input parameters. Features like this make PowerShell truly remarkable.
Get-SIDHistory –SamAccountName ashleym | Remove-SIDHistory
I recommend piping the output of the Remove-SIDHistory to a CSV file for change documentation.  Like this:
Get-SIDHistory –SamAccountName ashleym | Remove-SIDHistory | Export-CSV removed.csv
Your CSV will include a date/time stamp for each entry's removal:
image
This documentation will come in handy if users start to call and report issues after the removal.  But that shouldn't happen if you are thorough in scrubbing all SID history dependencies prior to removing the data.

Conclusion

If you've been putting off SID history remediation due to the mystery of how to remove it, then your puzzle is solved.  Using the functions in today's post will make the removal process swift and painless.  Let me reiterate that this is the LAST step in the migration process.  Do not proceed until all of your resource translation is complete and verified.  Phase the remediation by user groups to limit any unforeseen impact.  Following these recommendations will make the transition as smooth as possible.
So if you are celebrating Thanksgiving with family tomorrow and everyone around the table shares something they are thankful for, you can be the geek that is thankful for PowerShell.  Most likely no one else at the table will know what you're talking about.  Just smile and nod.

Coming Up Next…

In the next installment of our SID history series we'll provide a handy summary of each post in the series.  We will also take the function library we've been using and upgrade it to a PowerShell module.  Then we'll walk through the entire SID history remediation process using the provided cmdlets in this module.