Powershell: Fileservermigration to new Domain
Since a long time I work for a customer and he asked if I can shortly write a script to help him to migrate a lot of fileserver from one to a new Active Directory.
All former used tools had a lot of disadvantages because they would be very expensive, the logging was awful, they are slow, the functionality not what was expected, could not be central managed and therefore here we go: A simple, understandable, fast solution which can be adopt to all requirements. A masterpiece also if the code is not that beauty.
This script is ready to use after you modify the constant variables on the top but please do only use it if you understand what the script does.
For centrally management the script can executed with a Taskscheduler created over a Grouppolicy and the Logfiles are written on a central share. (not part of this Post)
conditions:
- SDDL is used
- the ACE´s are added and not replaced
- only not inherited permissions are added
- Buildin-Groups like Users can not be migrated but
- a mapping table is imported therefore you exchange Build-in Users with an Active Directory Group
- TokenAdjuster is used
very simple flow chart:
mapping.csv:
if you need to overwrite DU (Domain Users) with something else hereby a screenshot of the mapping.csv if needed
param([string]$path) cls $Searchbase = "OU=mymasterpiece,DC=com" $LDAPServer = "DC01.mymasterpiece.com" $targetdomain = "mymasterpiece" $User="mymasterpiece\readuser" $Pass='Pa$w0rd' $DomainSID = "S-1-5-21-38000000-18900000-300000032" $searcher = New-Object system.directoryservices.directorysearcher $domain = New-Object directoryservices.directoryentry("LDAP://$LDAPServer",$User,$Pass) $Scriptroot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath('.\') #Activate necessary admin privileges Try { [void][TokenAdjuster] } Catch { $AdjustTokenPrivileges = @" using System; using System.Runtime.InteropServices; public class TokenAdjuster { [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); [DllImport("kernel32.dll", ExactSpelling = true)] internal static extern IntPtr GetCurrentProcess(); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); [DllImport("advapi32.dll", SetLastError = true)] internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct TokPriv1Luid { public int Count; public long Luid; public int Attr; } internal const int SE_PRIVILEGE_DISABLED = 0x00000000; internal const int SE_PRIVILEGE_ENABLED = 0x00000002; internal const int TOKEN_QUERY = 0x00000008; internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; public static bool AddPrivilege(string privilege) { try { bool retVal; TokPriv1Luid tp; IntPtr hproc = GetCurrentProcess(); IntPtr htok = IntPtr.Zero; retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED; retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); return retVal; } catch (Exception ex) { throw ex; } } public static bool RemovePrivilege(string privilege) { try { bool retVal; TokPriv1Luid tp; IntPtr hproc = GetCurrentProcess(); IntPtr htok = IntPtr.Zero; retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_DISABLED; retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); return retVal; } catch (Exception ex) { throw ex; } } } "@ Add-Type $AdjustTokenPrivileges } [void][TokenAdjuster]::AddPrivilege("SeRestorePrivilege") #Necessary to override FilePermissions [void][TokenAdjuster]::AddPrivilege("SeBackupPrivilege") #Necessary to bypass Traverse Checking [void][TokenAdjuster]::AddPrivilege("SeTakeOwnershipPrivilege") #Necessary to set Owner Permissions function BuildSIDHashtable { Write-log "building SIDHashTable.... will take a few secounds" $hashtableSID = @{} $searcher = New-Object system.directoryservices.directorysearcher $domain = New-Object directoryservices.directoryentry("LDAP://$Searchbase",$User,$Pass) $searcher.SearchScope = "Subtree" $searcher.pagesize = 99999 $searcher.Searchroot = $domain $searcher.Filter = "(sIDHistory=*)" $SidHistory = $Searcher.findall() foreach ($Object in $SidHistory) { foreach ($SIDbinary in $Object.Properties.sidhistory) { $hashtableSID.Add((HexSIDToDec $SIDbinary),(HexSIDToDec $Object.Properties.objectsid[0])) } } return $hashtableSID } function Write-Log #function to log { [CmdletBinding()] Param ( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [Alias("LogContent")] [string]$Message, [Parameter(Mandatory=$false)] [Alias('LogPath')] [string]$Path='C:\temp\addACL.log', [Parameter(Mandatory=$false)] [ValidateSet("Error","Warn","Info")] [string]$Level="Info", [Parameter(Mandatory=$false)] [switch]$NoClobber ) Begin { # Set VerbosePreference to Continue so that verbose messages are displayed. $VerbosePreference = 'Continue' } Process { # If the file already exists and NoClobber was specified, do not write to the log. if ((Test-Path $Path) -AND $NoClobber) { Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." Return } # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. elseif (!(Test-Path $Path)) { Write-Verbose "Creating $Path." $NewLogFile = New-Item $Path -Force -ItemType File } else { # Nothing to see here yet. } # Format Date for our Log File $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" # Write message to error, warning, or verbose pipeline and specify $LevelText switch ($Level) { 'Error' { Write-Error $Message $LevelText = 'ERROR:' } 'Warn' { Write-Warning $Message $LevelText = 'WARNING:' } 'Info' { Write-Verbose $Message $LevelText = 'INFO:' } } # Write log entry to $Path "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append try {"$FormattedDate $LevelText $Message" | Out-File -FilePath $ADLogPath -Append -ErrorAction Stop} catch{} #nothing to do } End { } } Function HexSIDToDec($HexSID)#function to convert byte array SID to string { # Convert into normal array of bytes. $strSID = "S-" + $HexSID $arrSID = $strSID.Split(" ") $Max = $arrSID.Count $DecSID = $arrSID[0] + "-" + $arrSID[1] + "-" + $arrSID[8] If ($Max -eq 11) { Return $DecSID } $Temp1 = [Int64]$arrSID[12] + (256 * ([Int64]$arrSID[13] + (256 * ([Int64]$arrSID[14] + (256 * ([Int64]$arrSID[15])))))) $DecSID = $DecSID + "-" + $($Temp1) If ($Max -eq 15) { Return $DecSID } $Temp2 = [Int64]$arrSID[16] + (256 * ([Int64]$arrSID[17] + (256 * ([Int64]$arrSID[18] + (256 * ([Int64]$arrSID[19])))))) $DecSID = $DecSID + "-" + $($Temp2) $Temp3 = [Int64]$arrSID[20] + (256 * ([Int64]$arrSID[21] + (256 * ([Int64]$arrSID[22] + (256 * ([Int64]$arrSID[23])))))) $DecSID = $DecSID + "-" + $($Temp3) If ($Max -lt 24) { Return $DecSID } $Temp4 = [Int64]$arrSID[24] + (256 * ([Int64]$arrSID[25] + (256 * ([Int64]$arrSID[26] + (256 * ([Int64]$arrSID[27])))))) $DecSID = $DecSID + "-" + $($Temp4) Return $DecSID } function addfileACL #function to add ACL to Files { param ($Path) $Files = @() $Files += Get-Item $Path $Files += (Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue -Directory) Write-log "fnct addfileACL| need to proceed $($Files.count) files" -Level Info $i = 0 $outputcount = 500 $counter = 0 $result = @() $changemade = $false foreach ($File in $Files) { $result = @() $global:change = $false $ACL = $SDDL = $SDDLSplit = $null Write-log "Working on Object: $($File.FullName)" -Level Info $ACL = get-acl $File.FullName if (!($ACL)){continue} $SDDL = $ACL.Sddl $SDDLSplit = $SDDL.Split("(") | foreach {$_.trim(')') } foreach ($SDDLACE in $SDDLSplit) { $TargetSID = $null if ($SDDLACE -like "A;*") { if ($SDDLACE -notlike "*ID*") { $global:change = $true } } if (($SDDLACE.split(";")[5] -like "$($DomainSID)*") -or ($SDDLACE.split(";")[5] -like "DU")-and ($SDDLACE -notlike "O:S-1-5-21*") -and ($SDDLACE -notlike "*ID*") ) { $SourceSID = $SDDLACE.split(";")[5] $TargetSID = $SIDTable.($SourceSID) if ($TargetSID) { $result += $SDDLACE $result += $SDDLACE.Replace($SourceSID,$TargetSID) $changemade = $true if ($global:change) { Write-log "not inherited Permission found, adding $($TargetSID) to $($SourceSID)" } } } else { $result += $SDDLACE } } $result = $result | select -Unique if ($changemade) { $newSddl = @() $newSddl += $result | select -First 1 $newSddl += $result | select -Skip 1 | foreach {"($($_))"} $newSddl = $newSddl -Join $_ if ($global:change) { $ACL.SetSecurityDescriptorSddlForm($newSddl) Set-Acl -aclObject:$ACL -path:$File.FullName -ErrorAction stop Write-log "Write ACL to $($File.FullName)" $ACL = $SDDL = $SDDLSplit = $newSddl = $TargetSID = $SourceSID = $null } } else { Write-log "only inherited permissions, nothing to do for: $($File)" } $global:change = $false } } function AddMappingtable {param($path) Write-log "updateing SIDHashTable with mapping of $($path).... will take a few secounds" try {$csv = import-csv -Path $path -Delimiter ";" -ErrorAction Stop} catch {Write-log "Could not load Mappingtable from $($path) to update SIDHashTable"} foreach ($row in $csv) { if ($SIDTable.($row.Source)) { $SIDTable.($row.Source) = $row.Target Write-log "Replace Target for $($row.Source) with $($row.Target)" } else { $SIDTable.add($row.Source,$row.Target) Write-log "Add $($row.Source) with $($row.Target)" } } } $SIDTable = BuildSIDHashtable AddMappingtable -Table $SIDTable -path "$($Scriptroot)\mapping.csv" addfileACL -Path $path write-log "Script End"