by: Helge, published: Aug 4, 2010, updated: Mar 13, 2021, in

Free Script: User Profile Domain Migration with SetACL

This article is part of Helge’s Profile Toolkit, a set of posts explaining the knowledge and tools required to tame Windows user profiles.

When administrators migrate user accounts between domains, they typically re-ACL those server resources users have access to. In plain English: they copy or move the permissions from the accounts of the old domain to the corresponding accounts of the new domain. There are various ways to do that. In this article, I present a simple way to re-ACL roaming user profiles.

User profiles store permissions both in the file system and in the registry. When re-ACLing, we need to deal with both. The process as such can be described like this: Examine all access control entries (ACEs). If one with a user or group from the old domain is found, duplicate it and replace the user/group with the corresponding user/group from the new domain. Although that does not sound too bad, creating a tool from scratch that offers the required functionality would be quite a challenge (and a time killer).

SetACL to the Rescue

Luckily for you, all the difficult work has already been done by yours truly. SetACL, the powerful free tool for ACL management, has all the necessary logic built right in. We only need a lightweight script around the SetACL commands. If you take a look at DomainMigrateProfiles.cmd below, do not feel intimidated. It consists almost entirely of comments, error checks and logging. The actual migration logic is contained in the following four commands that load a registry hive, migrate registry permissions, migrate file system permissions and unload the hive. Here is a simplified version with sample data instead of variables:

reg load HKU\DomainMigrateProfiles "\\server\profiles\userA\NTUSER.DAT"
setacl -on HKU\DomainMigrateProfiles -ot reg -actn domain -dom "n1:OldDomain;n2:NewDomain;da:cpydom;w:dacl"
setacl -on "\\server\profiles\userA" -ot file -actn domain -dom "n1:OldDomain;n2:NewDomain;da:cpydom;w:dacl"
reg unload HKU\DomainMigrateProfiles

That looks simple, doesn’t it? And it is. Domain migration re-ACLing is not difficult if the right tools are used.

Using the Script DomainMigrateProfiles.cmd

There are three things you need before you can use the re-ACLing script DomainMigrateProfiles.cmd:

  1. The name of your old domain (can be a NetBIOS or DNS domain name)
  2. The name of your new domain (can be a NetBIOS or DNS domain name)
  3. A file with the paths to the profiles to re-ACL. One path per line.

Why did I not implement a simple directory listing in the script so it modifies all profiles in a file share? Because often enough, you do not want to re-ACL all profiles. You probably want to start with a test group, and even when you do the full migration, you might want to exclude certain admin or other special profiles.

But the file is created easily enough. Use the following scriptlet, replacing “C:\Users” with the folder containing your user profiles:

for /f "delims=" %i in ('dir /b /ad "C:\Users"') do @echo C:\Users\%i>>ProfileList.txt

This will create the file ProfileList.txt in the current directory. Once you have everything, you start the migration script like this:

DomainMigrateProfiles.cmd "ProfileList.txt" OldDomain NewDomain

Here is some sample output (as I mentioned earlier, most of the script consists of logic and such stuff, so expect nicely verbose messages, telling you exactly what goes on):

INFORMATION ============= Starting =============
INFORMATION Processing folder 'd:\temp\Profiles\test profile' ...
INFORMATION Loading registry hive 'HKU\DomainMigrateProfiles' from file 'd:\temp\Profiles\test profile\NTUSER.DAT' ...
INFORMATION Copying domain permissions on 'HKU\DomainMigrateProfiles' from OldDomain to NewDomain ...
INFORMATION Copying domain permissions on 'd:\temp\Profiles\test profile' from OldDomain to NewDomain ...
INFORMATION Unloading registry hive 'HKU\DomainMigrateProfiles' ...

In addition to the output on screen, all messages are written to the log file DomainMigrateProfiles.log in the script’s directory.

The Code

Without further ado, here is the script. In case you do not fancy copying it from here, just download it.

@echo off
::
:: DomainMigrateProfiles.cmd, version 1.0
::
:: Author: Helge Klein
::
:: Migrate user profiles between domains by copying permissions from a source domain to a target domain
::
:: This script uses SetACL (http://setacl.sourceforge.net) to duplicate the permissions for all
:: users and groups from a source domain to corresponding users and groups of a target domain.
::
:: Parameters:
::
:: 1) Path to file that contains the paths to the profiles to migrate. One path per line, e.g.:
::    C:\Data\Profiles\UserX
::    C:\Data\Profiles\UserY
::    C:\Data\Profiles\UserZ
:: 2) Name of source domain (NetBIOS or DNS name)
:: 3) Name of target domain (NetBIOS or DNS name)
::

:: Do not spill local variables into caller's environment
setlocal

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
:: Modify these global variables according to your needs
::
:: Remove/replace or copy permissions from old domain to new domain
:: See: http://setacl.sourceforge.net/html/doc-reference.html#domainaction
set DomainAction=cpydom
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

::
:: Variables
::
:: Path and name of log file
set LogFile="%~dpn0.log"

:: Log script start
call :LogMessage INFORMATION "============= Starting ============="

:: Input file
if not exist "%~1" (
	set ErrMsg=Please specify an existing file with a list of profile folders to migrate!
	goto ExitOnError
)
set ProfileList="%~1"

:: Source domain
if "%~2"=="" (
	set ErrMsg=Please specify the source domain as second parameter!
	goto ExitOnError
)
set SourceDomain=%~2

:: Target domain
if "%~3"=="" (
	set ErrMsg=Please specify the target domain as third parameter!
	goto ExitOnError
)
set TargetDomain=%~3

:: Registry path to mount hives to
set RegistryMountPath="HKU\DomainMigrateProfiles"

::
:: Start of script
::

:: Check prerequisites
if not exist SetACL.exe (
	set ErrMsg=SetACL not found!
	goto ExitOnError
)

:: Main loop - process each line from input file individually
for /f "delims=" %%i in ('type %ProfileList%') do call :ProcessSingleProfile "%%i"

:: Done
goto ExitOnSuccess


:::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
:: Subroutine:	Process a single profile
:: Parameters:
::		1: The current profile's number and individual ID
::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::

:ProcessSingleProfile

:: All variables created here are to be local
setlocal

:: Store parameters
:: No quotes on purpose!
set ProfilePath=%~1

:: Build path to NTUSER.DAT
set PathNTUSERDAT="%ProfilePath%\NTUSER.DAT"

:: Log progress
call :LogMessage INFORMATION "Processing folder '%ProfilePath%' ..."

:: Check if profile folder exists
if not exist "%ProfilePath%" (
	call :LogMessage WARNING "Folder '%ProfilePath%' does not exist. Skipping it."
	goto :EndOfFunction
)

:: Check if registry hive NTUSER.DAT exists in profile folder
if not exist %PathNTUSERDAT% (
	call :LogMessage WARNING "No registry hive NTUSER.DAT found here: '%PathNTUSERDAT%'. Skipping it."
	goto :EndOfFunction
)

:: Load the registry hive
call :LogMessage INFORMATION "Loading registry hive '%RegistryMountPath:"=%' from file '%PathNTUSERDAT:"=%' ..."
reg load %RegistryMountPath% %PathNTUSERDAT% 1>>%LogFile% 2>>&1
if ERRORLEVEL 1 (
	:: Something went wrong
	call :LogMessage ERROR "Loading the hive '%PathNTUSERDAT:"=%' to '%RegistryMountPath:"=%' failed. Skipping this profile."
	goto :EndOfFunction
)

:: Store that we loaded the hive
set RegHiveLoaded=1

:: Set registry permissions
call :LogMessage INFORMATION "Copying domain permissions on '%RegistryMountPath:"=%' from %Sourcedomain% to %TargetDomain% ..."
setacl -on %RegistryMountPath% -ot reg -actn domain -dom "n1:%SourceDomain%;n2:%TargetDomain%;da:%DomainAction%;w:dacl" 1>>%LogFile% 2>>&1
if ERRORLEVEL 1 (
	:: Something went wrong
	call :LogMessage ERROR "SetACL on '%RegistryMountPath:"=%' failed. Skipping this profile."
	goto :EndOfFunction
)

:: Set file system permissions
call :LogMessage INFORMATION "Copying domain permissions on '%ProfilePath%' from %Sourcedomain% to %TargetDomain% ..."
setacl -on "%ProfilePath%" -ot file -actn domain -dom "n1:%SourceDomain%;n2:%TargetDomain%;da:%DomainAction%;w:dacl" 1>>%LogFile% 2>>&1
if ERRORLEVEL 1 (
	:: Something went wrong
	call :LogMessage ERROR "SetACL on '%ProfilePath%' failed. Skipping this profile."
	goto :EndOfFunction
)

:EndOfFunction

:: Make sure we unload the hive
if defined RegHiveLoaded (
	call :LogMessage INFORMATION "Unloading registry hive '%RegistryMountPath:"=%' ..."
	reg unload %RegistryMountPath% 1>>%LogFile% 2>>&1
)

:: Restore original variable set
endlocal

:: End of subroutine
goto :eof

:::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
:: Subroutine:	Log a (info|warning|error) message
:: Parameters:
::		1: Severity (info|warning|error)
::		2: Message
::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::

:LogMessage

:: All variables created here are to be local
setlocal

echo %~1 %~2
echo %date% %time% %~1 %~2>>%LogFile%

:: Restore original variable set
endlocal

:: End of subroutine
goto :eof

:::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
::	Cleanup and exit
::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::

:ExitOnError

:: Print the error message and exit
call :LogMessage ERROR "%ErrMsg%"

exit /b 1

:ExitOnSuccess

exit /b

What This is Not

The method described here is by no means a full migration of a user’s individual configuration from one platform to another. It merely changes the security settings of user profiles in such a way that users from the new domain have access to profiles from the old domain.

During my testing I got the following SetACL error in the log: “Account was not found in domain .” This happened with the current version 2.0.3 of SetACL. This issue is fixed in SetACL 2.1 which will probably be released in September 2010.

Previous Article How to Generate Directory Listing with Full Path from Batch
Next Article What is the SYNCHRONIZE File Access Right?