generalize: assign the data-disk letter every boot, and heal a temp profile
The relocated profile went temporary on the target and stayed that way. The cause was not the profile: the image ships with ProfilesDirectory set to D:\Users but with no drive letter for the data disk. generalize strips MountedDevices, and the specialize pass that re-asserts the letter runs only in the build VM, never on the target -- so the zvol boots letterless, the first autologon cannot find D:\Users\sagar (event 1511), and Windows falls back to a temporary profile, renames the real ProfileList key to .bak, and the fault sticks on every later logon. Confirmed by reading the shipped image offline: ProfileList has the SID at D:\Users\TEMP with a .bak sibling at D:\Users\sagar, MountedDevices carries no \DosDevices\D:, and the sagar hive on the zvol is intact -- so nothing was wrong but the letter. An onstart SYSTEM task now runs the existing (idempotent) data-disk init, whose diskpart assign writes MountedDevices and so makes D: persistent for every later boot. Only the first boot is exposed to the race; if it left a .bak, a small PowerShell heal puts the key back, drops the temp profile, and reboots once -- after which D: is persistent and the real profile loads. Same onstart / SYSTEM mechanism the static address already uses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117qMyjpuXsjpVAcpJbFD8g
This commit is contained in:
parent
6a62a649bd
commit
d94c576df2
1 changed files with 55 additions and 1 deletions
|
|
@ -143,6 +143,49 @@ in
|
||||||
:done
|
: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) ''
|
folderLocationsXml = lib.optionalString (profilesDirectory != null) ''
|
||||||
<!-- Profiles live on the data disk, so the OS disk stays disposable
|
<!-- Profiles live on the data disk, so the OS disk stays disposable
|
||||||
and rebuilding it does not take the profile along -->
|
and rebuilding it does not take the profile along -->
|
||||||
|
|
@ -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
|
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) ''
|
${lib.optionalString (writeFilter != null) ''
|
||||||
:: Install the feature now, but defer configuring it: uwfmgr does not exist
|
:: 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
|
:: 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 = oobeXml; dest = "/oobe-unattend.xml"; }
|
||||||
{ source = postOobeScript; dest = "/post-oobe.cmd"; }
|
{ source = postOobeScript; dest = "/post-oobe.cmd"; }
|
||||||
{ source = masScript; dest = "/MAS_AIO.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 (writeFilter != null) { source = uwfConfigScript; dest = "/vmix-uwf-config.cmd"; }
|
||||||
++ lib.optional (staticIP != null) { source = staticIPScript; dest = "/vmix-static-ip.cmd"; };
|
++ lib.optional (staticIP != null) { source = staticIPScript; dest = "/vmix-static-ip.cmd"; };
|
||||||
# delayOobeRun: sysprep + shutdown — OOBE runs on real hardware
|
# delayOobeRun: sysprep + shutdown — OOBE runs on real hardware
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue