Oct 07

ADMT Cross-Forest migration, the PowerShell way!


A while back, I migrated around 29K Exchange 2007 mailboxes and users with one PowerShell migration script cross-forest to Exchange 2010 into an in-house developed multitenant provisioning platform. Along these mailboxes there were also all kinds of AD Tenant related objects. All objects had to be provisioned in multiple domains in the target forest. Because I don’t like any manual tasks or Vbscripts executed from PowerShell everything was automated with native PowerShell scripting only. And better yet, everything was centralised and executed from just one script embedded with multiple functions. This script is placed on a management server in the target domain which served as the initiator for all migration tasks.

Generally ADMT is installed on the target domain controller, the reason for that is to be able to copy the SID from the source user into the SIDhistory attribute onto the target user. Because of security reasons the DsAddSidHistory API function which is called by ADMT needs to be executed on a domain controller in the target domain, in turn the domain controller in the source domain selected for the SID copy operation needs to be the primary domain controller. Because I’m operating in a heavily used production environment, I tried to find a way where you won’t have to install or modify anything on the domain controllers, every change is one too many and could potentially lead to outage or even worse compromise the security. Besides that it’s cool to find a way to automate things and circumvent installers and extra layers of software, that way you can hook into the same API’s the software is using and write your own tailored made code/application on top of it, Since ADMT could operate on a member server I installed the ADMT package on the management server but then of course I would miss the SID copy functionality from ADMT. After investigating how the SID copy process worked I stumbled upon a custom made .NET assembly called SIDCloner which can be imported in PowerShell and also uses the DsAddSidHistory API. Without installing any software this assembly file can be placed on the target domain controller and remotely executed in a script block with the invoke-command cmdlet from the management (migration) station. Then I had to automate ADMT’s object and password copy process in PowerShell and that’s not that straight forward, most ADMT automation scripts and projects found are done with VB. The trick is using the “ADMT.Migration” COM object in PS, the object is installed as part of the ADMT installation and also used in VB scripts. If you want to copy passwords with ADMT then you also need a ‘Password Export’ server setup on the source domain controller, ADMT uses this server when you enabled the password copy option in the ADMT script.

Eventually I scripted everything in PS and ended up in custom made migration script with around 1.3K lines and 22 functions leaning on my multitenant provisioning script. This mutlitenant script actually replaces a full blown provisioning engine for AD/Exchange/Sharepoint/Lync/Hosted Destkop provisioning and is somewhere around 5K lines of code and is dotsourced in the migration script. By dotsourcing the provisioning script all functions and global variables are made available in the migration script, this way you seperate the functionality and code (version control) is maintained  per script.


Setting up ADMT’s installation and configuration is done quite easily and partially covered in this blog post, I kindly refer you to Technet or the numerous blog posts covering the topic. For the SIDcopy part you have to download SIDCloner, extract the assembly sidcloner.dll and place it on the filesysem of the target domaincontroller you’re going to use. There are three more steps to take before you are able to use SIDcloner. Creating the domain local group ($$$SourceDomain) in the source domain,  enabling SIDhistory on the trust between the two domains and enabling the auditing of success and failures in the account management section of the DC domain GPO.

If you’re going to use the ‘Password Export Server’ (PES) just follow this article, you will have to generate the encryption file via ADMT and use that file in the installation process of the PES MSI installer.

Also don’t forget to run the PES service under a privileged user account from the target domain. PES bits can be downloaded from here. The installation and download links all refer to Microsoft’s connect site, there you can find the latests versions, they work with 2012 R2 and they are customized to work with Microsoft Azure’s Active Directory.


Most COM objects are oldschool and therefore 32-bit, so if you want to use the “ADMT.migration” COM object in the ADMT migration script,  you have to execute it under a 32-bit PowerShell session.

Since I’m using 64-bit snappins and modules in a 64-bit PS runtime environment, the only quick and clean way is executing the ADMT migration task in a 32-bit job process with the ‘start-job’ cmdlet combined with the ‘-RunAs32’ parameter set to $true. My job executes the admtmig.ps1 script on the same management/migration box with all the constants and options required by ADMT. The admtmig.ps1 script requires two arguments, one tenant variable and one admtobjects variable. The Tenant argument is used to create two new AD canonical name (CN) variables in the script based on the existing source and new target OU. The admtobjects argument is an array of objects from the source domain to be migrated and are also in a canonical name (CN) format.  The following code executes the script and waits for the job to finish.

You have to adjust the admtmig.ps1 script (below) to your needs, scroll through the script and modify the variables like source/ target domain DC’s , password server and the native ADMT constants.

I left it pretty standard but you have to take a look at the properties I excluded, basically they are all Exchange properties which I didn’t need since I already read out and stored all source users and their related Exchange attributes in nested Hashtables. Eventually I converted all the values to the new Exchange multitenant provisioning scheme in the target domain. Another benefit in this approach is capturing the old configuration before ADMT or Exchange MRS (mailbox move) could modify it.

If you want to preserve existing ACL’s based on the source users SID you have to copy the SID. That’s where SIDCloner comes in play with the following code.

The copying process is done per user, instead of opening up a new session per user, invoke-command uses an existing session which has an idletimeout of 60 minutes. Invoke-command executes the scriptblock with four arguments and loads the assembly from the local path on the target domain controller. Then the .NET method ‘CloneSid’ is executed along with the four supplied arguments, which are pretty self-explanatory. Using this method it’s possible to copy a SID from one user to another user with a totally different SAM accountname, it could be any migrated or non-migrated user in the target domain. Now you probably understand the security implications of executing such command and why the API call is restricted to the domain controller.