diff --git a/lib/images/windows/templates/generalize.nix b/lib/images/windows/templates/generalize.nix index 52109d6..9c2e7e8 100644 --- a/lib/images/windows/templates/generalize.nix +++ b/lib/images/windows/templates/generalize.nix @@ -143,6 +143,49 @@ in :done ''; + # PowerShell in its own file, so neither cmd quoting nor Nix's '' need to be + # fought. If the account's real profile got backed up to a .bak key (the + # temporary-profile fallback), put it back: drop the temp key, rename .bak to + # the live SID, remove the temp directory, and drop a flag so the caller + # knows to reboot. + healProfileScript = pkgs.writeText "vmix-heal-profile.ps1" '' + $pl = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' + $bak = Get-ChildItem $pl -ErrorAction SilentlyContinue | Where-Object { + $_.PSChildName -like '*.bak' -and + (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).ProfileImagePath -eq '${profilesDirectory}\${username}' + } | Select-Object -First 1 + if ($bak) { + $sid = $bak.PSChildName -replace '\.bak$' + Remove-Item (Join-Path $pl $sid) -Recurse -Force -ErrorAction SilentlyContinue + Rename-Item $bak.PSPath $sid -ErrorAction SilentlyContinue + Remove-Item '${profilesDirectory}\TEMP' -Recurse -Force -ErrorAction SilentlyContinue + New-Item -Path C:\Windows\Temp\vmix-profile-healed -ItemType File -Force | Out-Null + } + ''; + + # Ensures D: exists on every boot and heals a profile that went temporary. + # + # generalize strips MountedDevices, so the shipped image carries no drive + # letter for the data disk, and the specialize pass that would re-assert it + # runs only in the build, not on the target. The zvol therefore boots + # letterless, the first autologon cannot find its relocated profile at + # ${profilesDirectory}\${username} (event 1511) and falls back to a temporary + # one, backing the real key up as .bak and making the fault stick. + # + # diskpart assign writes MountedDevices, so once this has run once D: is + # persistent for every later boot. Only the first boot is exposed, and if it + # left a .bak the heal puts it back and reboots -- the next boot, D: now + # persistent and ProfileList clean, logs straight into the real profile. + bootDataProfileScript = pkgs.writeText "vmix-data-profile.cmd" '' + @echo off + call C:\vmix-init-data-disk.cmd + powershell -NoProfile -ExecutionPolicy Bypass -File C:\vmix-heal-profile.ps1 > C:\Windows\Temp\vmix-data-profile.log 2>&1 + if exist C:\Windows\Temp\vmix-profile-healed ( + del /q C:\Windows\Temp\vmix-profile-healed + shutdown /r /t 5 /c "vmix: repaired relocated profile, restarting" + ) + ''; + folderLocationsXml = lib.optionalString (profilesDirectory != null) '' @@ -247,6 +290,13 @@ in schtasks /create /tn "vmix-static-ip" /tr "C:\vmix-static-ip.cmd" /sc onstart /ru SYSTEM /rl HIGHEST /f > nul 2>&1 ''} + ${lib.optionalString (dataDisk != null) '' + :: Ensures D: is assigned on every boot -- the image ships without a + :: persisted letter for the data disk -- and heals a profile that went + :: temporary before D: was ready. Onstart / SYSTEM, like the address task. + schtasks /create /tn "vmix-data-profile" /tr "C:\vmix-data-profile.cmd" /sc onstart /ru SYSTEM /rl HIGHEST /f > nul 2>&1 + ''} + ${lib.optionalString (writeFilter != null) '' :: Install the feature now, but defer configuring it: uwfmgr does not exist :: until this has been through a reboot, and the swapfile belongs on the @@ -357,7 +407,11 @@ in { { source = oobeXml; dest = "/oobe-unattend.xml"; } { source = postOobeScript; dest = "/post-oobe.cmd"; } { source = masScript; dest = "/MAS_AIO.cmd"; } - ] ++ lib.optional (dataDisk != null) { source = initDataDiskScript; dest = "/vmix-init-data-disk.cmd"; } + ] ++ lib.optionals (dataDisk != null) [ + { source = initDataDiskScript; dest = "/vmix-init-data-disk.cmd"; } + { source = healProfileScript; dest = "/vmix-heal-profile.ps1"; } + { source = bootDataProfileScript; dest = "/vmix-data-profile.cmd"; } + ] ++ lib.optional (writeFilter != null) { source = uwfConfigScript; dest = "/vmix-uwf-config.cmd"; } ++ lib.optional (staticIP != null) { source = staticIPScript; dest = "/vmix-static-ip.cmd"; }; # delayOobeRun: sysprep + shutdown — OOBE runs on real hardware