Out of pure frustration with the fact that the Active Directory Migration Tool (ADMT) is unable (unwilling is my guess) to do security translation for users’ Remote Desktop Services (RDS) roaming profiles, I decided to take matters into my own hands and created the script below. It is not very refined just now, but I have a lot of ideas for future versions. In the meantime, if you can use it for something; great!
# Morgan Simonsen
# Script to translate a user profile belonging to a migrated user.
# Primarily intended to solve the problem with ADMT not translating
# Remote Desktop Service roaming profiles.
# Version: 1.0 (13.02.2012)
# Initial version.
# User changeable strings
# Only edit in this section!
# Root of folder or sharing storing the profiles
$RDSProfileRootDirectory = "Z:" # Include trailing backslash
# NetBIOS Name of source domain; the domain the user was migrated from
$SourceNBTDomainName = "DOMAIN_1" # Include trailing backslash
# NetBIOS Name of target domain; the domain the user was migrated to
$TargetNBTDomainName = "DDS" # Include trailing backslash
# DNS name of target domain
$TargetDNSDomainName = "dds.intern"
# FQDN of Domain Controller in target domain
$TargetDomainDC = "ddsdc1.dds.intern"
# Location of subinacl.exe
$SUBINACLLocation = "C:Program Files (x86)Windows Resource KitsToolssubinacl.exe"
# Location of echoArgs.exe (not actually used by the script)
$echoArgs = ($PSHome+"ModulesPscxAppsechoArgs.exe")
Get-ChildItem $RDSProfileRootDirectory | ForEach `
Write-Host ("Processing folder: "+$_.name)
If (Get-QADUser ($TargetNBTDomainName+$_.name) -service $TargetDomainDC)
$user = Get-QADUser ($TargetNBTDomainName+$_.name) -service $TargetDomainDC
Write-Host (" Found matching user: "+$user.Userprincipalname)
If ( (Test-Path ($_.FullName+"NTUSER.DAT")) -or (Test-Path ($_.FullName+"NTUSER.MAN")) )
Write-Host (" Found user registry hive: "+($_.FullName+"NTUSER.DAT"))
Write-Host (" Updating permissions...")
$entireprofile = ($_.FullName+"*.*")
$completeusername = ($TargetNBTDomainName+$user.samaccountname)
Write-Host (" SubInACL.exe File Output:")
& $SUBINACLLocation /noverbose /subdirectories $entireprofile /grant="$completeusername=f" /setowner=$completeusername > $null
Write-Host (" SubInACL.exe Registry Output:")
reg.exe load ("HKU"+$_.Name) ($_.FullName+"NTUSER.DAT") > $null
$regkey = ("HKEY_USERS"+$_.Name)
& $SUBINACLLocation /noverbose /subkeyreg $regkey /grant="$completeusername=f" > $null
reg.exe unload ("HKU"+$_.Name) > $null
Write-Host " Cannot find user in domain that matches folder name."
Write-Host " Continuing with next user."
The good old Active Directory Migration Tool (ADMT) has reached version 3.2 making it compatible with Windows 7/Server 2008 R2 and x64. ADMT started it’s Microsoft life as licensed software from One point. I’ve been using this baby since version 2.0. It offers what you need to perform intra or inter-forest Active Directory migrations/restructures, but it is very basic. The most annoying thing is that Microsoft have yet to implement PowerShell support in ADMT.
Here are some observations I have made about the use of ADMT over the course of many years:
- No version of ADMT translates security for Remote Desktop (Terminal Server) Roaming Profiles.
It will translate Local Profiles using the User Profiles option in the Computer Account Migration wizard, and regular Roaming Profiles using the Translate Roaming Profiles option in the User Account Migrationwizard, but it will not touch the Remote Desktop profile. This seems very strange to me since we are just talking about reading another attribute in Active Directory and doing the same thing as you are already doing with Roaming Profiles. Beats me…(Just to be clear, the regular Roaming Profile is specified on the Profile tab of the user in Active Directory Users and Computers (ADUC), and is stored in the profilePath attribute. The Remote Desktop Services Roaming profile is specified on the Remote Desktop Services Profile tab in ADUC and is stored in the userParameters attribute data blob. In the Windows Server 2008 Active Directory schema; Microsoft have created a new attribute called msTSProfilePath that is meant to store this setting in the future.)
- ADMT needs Full Control NTFS permissions to translate a profile, roaming or local.
Translating a profile means updating the NTFS permissions on all the files in the profile directory, but also loading the user’s registry hive; the NTUSER.DAT file, and translating the registry permissions set on all the keys in it. I have written about manually performing this process here.If you have not changed the default settings Windows will create roaming profiles with the following ACL/Owner:
– SYSTEM – Full Control
– user_name – Full Control
– Owner = user_name
Obviously these permissions will not let ADMT, which connects to the computer as the user running it (it does not have a service), translate the profile. To work around this you can either enable a Group Policy setting called Computer ConfigurationPoliciesAdministrative TemplatesSystemUser ProfilesAdd the Administrators security group to the roaming user profiles, before the profile is created or you can grant the Administrators local group (or any other user/group you prefer) Full Control permissions to the profile folder and all its files and subfolders. The easiest way to do the latter, in my opinion, is to use PsExec from Sysinternals to start a command shell as SYSTEM and then use CACLS.EXE/XCACLS.EXE/ICACLS.EXE, or any other ACL editor, to add the Administrators group to the files/folders. Here’s an example using CACLS.EXE (which is included in every version of Windows since Windows 2000):
1. Start the shell as SYSTEM: psexec.exe -i -s cmd.exe
2. Change the permissions with CACLS.EXE: cacls.exe <root of profile share> /T /E /C /G Administrators:F
- ADMT uses Server Message Block (SMB) and Remote Procedure Calls (RPC) respectively, to install and communicate with it’s agents on machines when performing computer migrations or security translations.
This means that you need to allow traffic from the ADMT machine to all the computers that you are going to migrate or translate security on, on these ports:
|RPC Endpoint mapper
||The endpoint mapper is like the RPC address book. Whenever an RPC connection is to be made; the client, in this case the ADMT machine, first connects to the endpoint mapper to find out which dynamic port the service it needs to talk to is listening on. It then makes a connection to that port.
|RPC Dynamic Ports
||Up to Windows Server 2008/Vista:
1024 – 65535 TCPWindows Server 2008/Vista and later:
49152 – 65535 TCP
|Any service using RPC talks to the RPC subsystem on the system where it is starting and asks for a port(s). This operation is performed each time the service starts and thus the port can potentially change on every service startup.
|Server Message Block (SMB)
||SMB Direct Hosting:
445 TCPNetBIOS Over TCP/IP: 137/UDP
|ADMT connects to the ADMIN$ share on computers it will either migrate or perform security translation on, and copies its files to the %windir%OnePointDomainAgent folder. It also installs a service on the machine called Active Directory Migration Service.
- ADMT Agent source files
Are stored in %windir%ADMTNT4Agent for Windows NT machines, or %windir%ADMTW2kAgent for Windows 2000 and above.
- ADMT cannot migrate a user object that has child objects
This is true for both inter and intra-forest migrations. Trying to do so will give you this error:
ERR2:7422 Failed to move source object <RDN of user being migrated>. hr=0x8007208c The operation cannot be performed because child objects exist. This operation can only be performed on a leaf object.
The only case where I have found child objects for users are with Exchange Active Sync partnerships. These are stored in a sub container, called ExchangeActiveSyncDevices, as objects of class msExchActiveSyncDevice. Each device the user has an active partnership with has its own object. A typical name of the device object is iPhone§Appl690509FTA4S. It contains attributes of the device, ID, firmware, protocol version etc.You will have to get rid of these one way or the other if you want to migrate the user. If you just delete them, the user needs to set up sync on their devices from scratch once they are migrated. This is usually not a major problem if the users are warned beforehand that this will happen. Another way is to export the sub container and all the device objects, delete it (leaving the user as a leaf object), migrating, and finally importing the device objects back under the migrated user. This last solution requires that you change the distinguishedName attribute when you import the data, and it is probably not supported by Microsoft…at all. Here are some commands using LDIFDE.EXE to export and import the data. You can use this in a script before and after user migration with ADMT:
ldifde.exe –s <source domain DC> –f <export file> –d <DN of the ExchangeActiveSyncDevices container> -l * -o objectGUID
Note that I have excluded the objectGUID attribute and included all others (-l *). When you import an object into Active Directory it will be assigned a new GUID. Importing a GUID, potentially causing two objects to have the same GUID, is a security violation, and the DC will refuse to perform it.
ldifde.exe -i –f <import file> –c <<old DN> replace with <new DN> (see LDIFDE help for more info)>
Now, one thing about this. The device object has an attribute called msExchUserDisplayName. It has a value in the form of e.g.: <domain DNS name>/OU/OU/<user>. In other words an LDAP canonical name. If you perform and export/import as I have shown here, this attribute will contain the canonical name the user had in the source domain, i.e. the OLD canonical name. I know for a fact that this will break remote wipe and the Get-ActiveSyncDeviceStatistics cmdlet on Exchange 2007, and probably Exchange 2010. However, the device will continue to sync. (On a side note, this problem also occurs if you move a user within a domain. The msExchUserDisplayName attribute is not updated; breaking wipe and the cmdlet.) To work around this you can try to exclude the attribute when exporting with LDIDFE.EXE or implement code that changes it before you import it back into your target domain. I have not, as of now, tried any of these yet, but I am thinking that if I use PowerShell instead of LDIFDE.EXE to export the objects it would be easy to replace the old canonical name with the new one. I also do not know if removing the attribute from the device object will break the sync partnership. Another problem though, is the device. It stores the domain and username when you set up the account and usually won’t let you change it. The only thing you get to change is the password. So all this work may be for nothing, since users must update the account information on their devices anyway. Damn!
- Inter-forest migrations do not exclude any attributes (except the SID…sort of)
According to the ADMT documentation a system attribute exclusion list is created the very first time you run a user migration. The attributes in the list always include the attributes mail and proxyAddresses. I addition, says the docs, the objectGUID, objectSID and legacyExchangeDn attributes are also always excluded even if they are not on the system attribute exclusion list. However, when you do intra-forest migrations ALL attributes are copied. You can examine the system attribute exclusion list with this script, found in KB937537:
Set o = CreateObject(“ADMT.Migration”)
When I ran this on an ADMT machine used for intra-forest migrations it returnes exactly zero attributes. Examining the migrated objects also verified that indeed all attributes had been copied. The mail, proxyAddresses, objectGUID and legacyExchangeDN were the same. The object had been given a new SID, which it had to because it was now living in a domain with a different domain SID, but the old SID had been copied into the sIDHistory attribute. Neat!
I will probably update this post with more info in the future. For now; Happy migrating!