Profile zu FSLogix migrieren

Lokale oder Roaming Profile zu FSLogix migrieren
Lokale oder Roaming Profile zu FSLogix migrieren

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! 🙂


Die gesuchte Lösung noch nicht gefunden oder benötigen Sie Hilfe bei anderen Themen aus meinem Blog? Nehmen Sie gerne Kontakt mit mir bzw. meinem Unternehmen Jan Mischo IT auf. Ich freue mich auf Ihre Anfrage: https://janmischo.it/kontakt/


+49 2801 7004300

info@janmischo.it

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..