
Vor langer Zeit, in einem weit entfernten Königreich, musste ich mehrere Citrix UPM Profile zu FSLogix migrieren. Damals reichte eine kleine Modifikation an diesem Script (Convert Citrix UPM to FSLogix Profile Containers – Xenit) aus. Dieses Mal stand die Migration von bestehenden lokalen Profilen an und es entstand diese „PoC“ Weiterentwicklung (samt Weiterentwicklung zur Migration von klassischen Roaming Profiles) vom o.g. Script.
WICHTIG: Vor Benutzung sollte das Script – selbstredend -getestet werden. Nach hoffentlich erfolgreicher Evaluierung solltet Ihr wohl auch nicht gleich aufs Ganze gehen, sondern nur eine Handvoll an Keyusern migrieren!
Jetzt aber genug der nicht so vielen Wort. Hier das Script:
#Requires -RunAsAdministrator
param(
[Parameter(Mandatory, Position = 0)]
[ValidateSet("Local", "Roaming")]
[string]$Type,
[Parameter(Mandatory, Position = 1)]
[ValidateScript({
Test-Path -Path $_ `
-PathType Container
})]
[string]$ProfilePath,
[Parameter(Mandatory, Position = 2)]
[ValidateScript({
Test-Path -Path $_ `
-PathType Container
})]
[string]$FSLogixPath,
[ValidateRange(1024,53687091200)]
[double]$VHDXSize = 30GB,
[switch]$FlipFlop
)
function Write-Log{
param(
[Parameter(Mandatory=$true, Position=0)]
[string]$LogPath,
[Parameter(Mandatory=$true, Position=1)]
[string]$LogText,
[Parameter(Mandatory=$true, Position=2)]
[ValidateSet("Info", "Warning", "Error")]
[string]$LogLevel
)
$Path = $LogPath.TrimEnd($LogPath.Split("\")[$LogPath.Split("\").Count-1])
if(-not (Test-Path -Path $Path -PathType Container)){
New-Item -Path $Path `
-ItemType Directory `
-Force |
Out-Null
}
"{0} [{1}]: {2}" -f `
$((Get-Date).ToString("yyyyMMdd-HHmmss")),
$LogLevel,
$LogText |
Out-File -FilePath $LogPath `
-Force `
-Append `
-Encoding utf8
}
if([string]::IsNullOrEmpty($PSScriptRoot)){
$Scriptpath = $psISE.CurrentFile.FullPath -replace $psISE.CurrentFile.DisplayName
$Scriptpath = $Scriptpath.TrimEnd("\")
} else{
$Scriptpath = $PSScriptRoot
}
$Logname = -join (
$((Get-Date).ToString("yyyyMMdd-HHmmss")),
"-Log.txt"
)
$Logfile = Join-Path -Path $Scriptpath `
-ChildPath $Logname
$Profiles = Get-ChildItem -Path $ProfilePath |
Where-Object { $_.Name -notmatch "^Administrator|^Public$|^Default$" }
[string]$FSLDiskSize = ($VHDXSize / 1048576).ToString()
foreach($p in $Profiles){
Write-Host "Migriere Profil von `"$($p.Name)`"..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Start $($p.Name)" `
-LogLevel Info
switch($Type){
"Roaming" {
$SamAccountName = Split-Path (($p -replace ".V(2|4|6)", "") -split ".$env:userdomain")[0] -Leaf
}
Default {
$SamAccountName = Split-Path ($p -split ".$env:userdomain")[0] -Leaf
}
}
$SID = (New-Object System.Security.Principal.NTAccount($SamAccountName)).Translate([System.Security.Principal.SecurityIdentifier]).Value
Write-Log -LogPath $Logfile `
-LogText "SamAccountName: $SamAccountName" `
-LogLevel Info
Write-Log -LogPath $Logfile `
-LogText "SId: $SID" `
-LogLevel Info
$RegContent = New-Object System.Collections.ArrayList
$RegContent.Add("Windows Registry Editor Version 5.00") |
Out-Null
$RegContent.Add("") |
Out-Null
$RegContent.Add("[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$SID]") |
Out-Null
$RegContent.Add("`"Flags`"=dword:00000000") |
Out-Null
$RegContent.Add("`"FSL_OriginalProfileImagePath`"=`"C:\\Users\\$SamAccountName`"") |
Out-Null
$RegContent.Add("`"ProfileImagePath`"=`"C:\\Users\\$SamAccountName`" ") |
Out-Null
$RegContent.Add("`"ProfileLoadTimeLow`"=dword:00000000") |
Out-Null
$RegContent.Add("`"ProfileLoadTimeHigh`"=dword:00000000") |
Out-Null
$RegContent.Add("`"RefCount`"=dword:00000000") |
Out-Null
$RegContent.Add("`"RunLogonScriptSync`"=dword:00000000") |
Out-Null
$RegContent.Add("`"State`"=dword:00000000") |
Out-Null
if(-not $FlipFlop.IsPresent){
$FSLFolder = Join-Path -Path $FSLogixPath `
-ChildPath ($SID+"_"+$SamAccountName)
} else{
$FSLFolder = Join-Path -Path $FSLogixPath `
-ChildPath ($SamAccountName+"_"+$SID)
}
if(-not (Test-Path -Path $FSLFolder -PathType Container)){
Write-Host "Erstelle FSLogix Profilordner `"$FSLFolder`"" `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "FSLogix Profilpfad: $FSLFolder" `
-LogLevel Info
New-Item -Path $FSLFolder `
-ItemType Directory |
Out-Null
}
Write-Host "Setze Berechtigungen..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Berechtige Profil Root" `
-LogLevel Info
Invoke-Expression -Command "C:\Windows\System32\icacls.exe $FSLFolder /setowner `"$env:userdomain\$SamAccountName`" /T /C"
Invoke-Expression -Command "C:\Windows\System32\icacls.exe $FSLFolder /grant `"$env:userdomain\$SamAccountName`:(OI)(CI)F`" /T"
$vhd = Join-Path -Path $FSLFolder `
-ChildPath ("Profile_"+$SamAccountName+".vhdx")
if(-not (Test-Path -Path $vhd -PathType Leaf)){
$DiskpartTempScript = Join-Path -Path $env:TEMP `
-ChildPath "DsikPartScript.txt"
Set-Content -Path $DiskpartTempScript `
-Value "create vdisk file=`"$vhd`" maximum $FSLDiskSize type=expandable" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "sel vdisk file=`"$vhd`"" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "attach vdisk" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "create part prim" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "select part 1" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "format fs=ntfs quick" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "assign letter=T" `
-Encoding UTF8
Write-Host "`nErstelle neue VHDX per diskpart Script `"$DiskpartTempScript`"..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Führe Diskpart Script aus ($DiskpartTempScript)" `
-LogLevel Info
Write-Log -LogPath $Logfile `
-LogText "Erstelle neue Disk $vhd" `
-LogLevel Info
Invoke-Expression -Command "C:\Windows\System32\diskpart.exe /s $DiskpartTempScript"
Invoke-Expression -Command "C:\windows\System32\label.exe T: Profile-$SamAccountName"
Write-Host "`nErstelle `"Profile`" Ordner im Root..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Erstelle Profile Ordner im Root der VHDX" `
-LogLevel Info
New-Item -Path "T:\Profile" `
-ItemType Directory |
Out-Null
Write-Host "Setze Berechtigungen auf `"Profile`" Ordner..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Berechtige Profil in der VHDX" `
-LogLevel Info
Invoke-Expression -Command "C:\Windows\System32\icacls.exe T:\Profile /setowner SYSTEM"
Invoke-Expression -Command "C:\Windows\System32\icacls.exe T:\Profile /inheritance:r"
Invoke-Expression -Command "C:\Windows\System32\icacls.exe T:\Profile /grant `"SYSTEM`:`(OI`)`(CI`)F`""
Invoke-Expression -Command "C:\Windows\System32\icacls.exe T:\Profile /grant `"*S-1-5-32-544`:`(OI`)`(CI`)F`""
Invoke-Expression -Command "C:\Windows\System32\icacls.exe T:\Profile /grant `"$env:userdomain\$SamAccountName`:`(OI`)`(CI`)F`""
Write-Log -LogPath $Logfile `
-LogText "Entferne Diskpart Script ($DiskpartTempScript)" `
-LogLevel Info
Remove-Item -Path $DiskpartTempScript `
-Confirm:$false `
-Force
}else{
$DiskpartTempScript = Join-Path -Path $env:TEMP `
-ChildPath "DsikPartScript.txt"
Set-Content -Path $DiskpartTempScript `
-Value "sel vdisk file=`"$vhd`"" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "attach vdisk" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "sel part 1" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "assign letter=T" `
-Encoding UTF8
Write-Host "`nVHDX bereits vorhanden. Disk wird gemounted..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Führe Diskpart Script aus ($DiskpartTempScript)" `
-LogLevel Info
Write-Log -LogPath $Logfile `
-LogText "Mounte bestehende Disk $vhd" `
-LogLevel Info
Invoke-Expression -Command "C:\Windows\System32\diskpart.exe /s $DiskpartTempScript"
Invoke-Expression -Command "C:\windows\System32\label.exe T: Profile-$SamAccountName"
Write-Log -LogPath $Logfile `
-LogText "Entferne Diskpart Script ($DiskpartTempScript)" `
-LogLevel Info
Remove-Item -Path $DiskpartTempScript `
-Confirm:$false `
-Force
}
Write-Host "`nKopiere Profil $($p.FullName) nach $vhd..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Kopiere Profil von $($p.FullName) in $vhd" `
-LogLevel Info
Write-Host "Robocopy Log: `"$Scriptpath\RCLog_$SamAccountName.txt`"" `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Robocopy Log: $Scriptpath\RCLog_$SamAccountName.txt" `
-LogLevel Info
Invoke-Expression -Command "C:\windows\System32\Robocopy.exe `"$($p.FullName)`" `"T:\Profile`" /MIR /R:0 /W:0 /XJD /unilog:`"$Scriptpath\RCLog_$SamAccountName.txt`""
if(-not (Test-Path -Path "T:\Profile\AppData\Local\FSLogix" -PathType Container)){
New-Item -Path "T:\Profile\AppData\Local\FSLogix" `
-ItemType Directory |
Out-Null
}
if(-not (Test-Path "T:\Profile\AppData\Local\FSLogix\ProfileData.reg" -PathType Leaf)){
Out-File -FilePath "T:\Profile\AppData\Local\FSLogix\ProfileData.reg" `
-InputObject $RegContent `
-Encoding ascii
}
$DiskpartTempScript = Join-Path -Path $env:TEMP `
-ChildPath "DsikPartScript.txt"
Set-Content -Path $DiskpartTempScript `
-Value "sel vdisk file=`"$vhd`"" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "sel part 1" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "remove letter=T" `
-Encoding UTF8
Add-Content -Path $DiskpartTempScript `
-Value "detach vdisk" `
-Encoding UTF8
Write-Host "`nDisk abhängen und aufräumen..." `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Führe Diskpart Script aus ($DiskpartTempScript)" `
-LogLevel Info
Write-Log -LogPath $Logfile `
-LogText "Dismounte Disk $vhd" `
-LogLevel Info
Invoke-Expression -Command "C:\Windows\System32\diskpart.exe /s $DiskpartTempScript"
Write-Log -LogPath $Logfile `
-LogText "Entferne Diskpart Script ($DiskpartTempScript)" `
-LogLevel Info
Remove-Item -Path $DiskpartTempScript `
-Confirm:$false `
-Force
Write-Host "Profil von `"$($p.Name)`" migriert!" `
-ForegroundColor Cyan
Write-Log -LogPath $Logfile `
-LogText "Ende $($p.Name)" `
-LogLevel Info
}
In diesem Sinne: Gutes Gelingen beim Profile zu FSLogix migrieren! 🙂
Schreibe einen Kommentar