windows/seal: leave nothing on C:, activate on the fresh SID, unmount the CD

Sealed VMs were left with setup scripts on C:\, the config CD mounted,
and Windows unactivated. Rework the first-boot flow so a settled VM
carries no vmix artifacts:

- Activation moves off the throwaway bootstrap's boot 1 into the boot-2
  finalize, which runs as the real account after its fresh SID/profile
  exist -- MAS on that SID is what actually sticks. MAS is kept until
  then, run once, and deleted.
- The static address is set once into the persistent store on the target
  NIC (sealed images already OOBE on the real NIC), so no per-boot task
  and no script survive on disk.
- Boot-2 finalize wipes every vmix-*, MAS_AIO.cmd, config and marker off
  C:\ after use, retires the bootstrap account/profile, and self-deletes.
- The config CD is unmounted for good -- drop its letter and disable the
  mount manager's auto-lettering (D: keeps its explicit assignment).

Non-configMedium generalize (the shared path) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117qMyjpuXsjpVAcpJbFD8g
This commit is contained in:
Git Sagar 2026-09-19 07:15:43 -03:00
parent b7838f5501
commit 6d4b9155d6

View file

@ -225,19 +225,47 @@ in
# Fires at the real account's first logon (HKLM RunOnce = next user to log # Fires at the real account's first logon (HKLM RunOnce = next user to log
# on), i.e. after the reboot below, once the bootstrap is no longer in use. # on), i.e. after the reboot below, once the bootstrap is no longer in use.
Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' ` Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' `
-Name vmixUserCleanup -Value 'cmd /c C:\vmix-user-cleanup.cmd' -Type String -Name vmixFinalize -Value 'cmd /c C:\vmix-finalize.cmd' -Type String
''; '';
# Runs once as the real account (RunOnce, after the hand-over reboot): retire # Runs once as the real account (RunOnce, after the hand-over reboot), so the
# the bootstrap account and its profile, and apply the per-user desktop tint # fresh machine SID and the real profile already exist. This is where activation
# (which the bootstrap ran against on the first boot, before this account # belongs -- on that fresh SID, not the throwaway bootstrap's. It then finishes
# existed). Bootstrap is idle here, so its profile is safe to remove. # per-user setup, retires the bootstrap, unmounts the config CD for good, and
userCleanupScript = pkgs.writeText "vmix-user-cleanup.cmd" '' # wipes every vmix artifact off C:\ so the running machine carries no leftover
# setup files. Self-deletes last.
finalizeScript = pkgs.writeText "vmix-finalize.cmd" ''
@echo off @echo off
:: 1) Activate Windows on the real account's fresh SID (TSforge, offline).
cscript //nologo C:\Windows\System32\slmgr.vbs /ipk M7XTQ-FN8P6-TTKYV-9D4CC-J462D
cscript //nologo C:\Windows\System32\slmgr.vbs /rilc
net stop sppsvc /y 2>nul
net start sppsvc
ping -n 8 127.0.0.1 >nul
if exist C:\MAS_AIO.cmd ( echo. | call C:\MAS_AIO.cmd /Z-Windows )
if exist "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" (
if exist C:\MAS_AIO.cmd ( echo. | call C:\MAS_AIO.cmd /Ohook )
)
:: 2) Per-user desktop tint, now that the real account is logged in.
powershell -NoProfile -ExecutionPolicy Bypass -File C:\vmix-apply-config.ps1 >nul 2>&1
:: 3) Retire the bootstrap account and its profile (idle now).
powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-CimInstance Win32_UserProfile | Where-Object { $_.LocalPath -like '*\${username}' } | Remove-CimInstance -ErrorAction SilentlyContinue" powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-CimInstance Win32_UserProfile | Where-Object { $_.LocalPath -like '*\${username}' } | Remove-CimInstance -ErrorAction SilentlyContinue"
net user ${username} /delete >nul 2>&1 net user ${username} /delete >nul 2>&1
powershell -NoProfile -ExecutionPolicy Bypass -File C:\vmix-apply-config.ps1 >nul 2>&1 :: 4) Unmount the config CD and keep it unmounted -- drop its letter and stop
del /q C:\vmix-user-cleanup.cmd 2>nul :: the mount manager auto-lettering it (D: keeps its explicit assignment).
powershell -NoProfile -ExecutionPolicy Bypass -Command "Get-CimInstance Win32_Volume -Filter 'DriveType=5 AND DriveLetter IS NOT NULL' | ForEach-Object { $_.DriveLetter=$null; [void]($_ | Set-CimInstance) }"
> C:\Windows\Temp\vmix-am.txt echo automount disable
>> C:\Windows\Temp\vmix-am.txt echo automount scrub
diskpart /s C:\Windows\Temp\vmix-am.txt >nul 2>&1
del /q C:\Windows\Temp\vmix-am.txt 2>nul
:: 5) Wipe every vmix setup artifact from C:\.
del /q C:\MAS_AIO.cmd C:\vmix-config.ps1 C:\vmix-data-initialized 2>nul
del /q C:\vmix-load-config.cmd C:\vmix-apply-config.ps1 C:\vmix-create-user.ps1 2>nul
del /q C:\vmix-init-data-disk.cmd C:\vmix-data-profile.cmd C:\vmix-heal-profile.ps1 2>nul
del /q C:\vmix-static-ip.cmd C:\vmix-static-ip.ps1 2>nul
del /q C:\oobe-unattend.xml C:\post-oobe.cmd 2>nul
:: 6) Self-delete.
(goto) 2>nul & del "%~f0"
''; '';
# Thin launcher, so the scheduled task has a cmd to point at. # Thin launcher, so the scheduled task has a cmd to point at.
@ -513,6 +541,7 @@ in
powershell -Command "Get-AppxPackage *MicrosoftEdgeDevToolsClient* | Remove-AppxPackage -ErrorAction SilentlyContinue" powershell -Command "Get-AppxPackage *MicrosoftEdgeDevToolsClient* | Remove-AppxPackage -ErrorAction SilentlyContinue"
${lib.optionalString (!configMedium) ''
:: Re-install product key and licenses to restore activation IDs after sysprep :: Re-install product key and licenses to restore activation IDs after sysprep
cscript //nologo C:\Windows\System32\slmgr.vbs /ipk M7XTQ-FN8P6-TTKYV-9D4CC-J462D cscript //nologo C:\Windows\System32\slmgr.vbs /ipk M7XTQ-FN8P6-TTKYV-9D4CC-J462D
cscript //nologo C:\Windows\System32\slmgr.vbs /rilc cscript //nologo C:\Windows\System32\slmgr.vbs /rilc
@ -531,6 +560,9 @@ in
) )
) )
del /q C:\MAS_AIO.cmd 2>nul del /q C:\MAS_AIO.cmd 2>nul
''}
:: configMedium: activation is deferred to the boot-2 finalize, so it runs
:: on the real account's fresh SID. MAS_AIO.cmd is kept for it until then.
${lib.optionalString enableRDP '' ${lib.optionalString enableRDP ''
:: Enable RDP :: Enable RDP
@ -556,7 +588,7 @@ in
powercfg /hibernate off powercfg /hibernate off
''} ''}
${lib.optionalString (staticIP != null || configMedium) '' ${lib.optionalString (staticIP != null && !configMedium) ''
:: This VM's only NIC sits on a macvtap bridged to the host's LAN, so its :: This VM's only NIC sits on a macvtap bridged to the host's LAN, so its
:: address is a LAN address that nothing hands out -- the guest asserts it. :: address is a LAN address that nothing hands out -- the guest asserts it.
:: Registered to run at every boot rather than applied here. OOBE runs in :: Registered to run at every boot rather than applied here. OOBE runs in
@ -568,10 +600,20 @@ 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) '' ${lib.optionalString configMedium ''
:: configMedium (sealed images) run OOBE on the real target NIC, so the
:: address is set here once and left in the persistent store -- it survives
:: reboots on its own, no per-boot task and no script left on disk. Runs on
:: this bootstrap boot so the real account is already reachable on boot 2.
powershell -NoProfile -ExecutionPolicy Bypass -File C:\vmix-static-ip.ps1 > C:\Windows\Temp\vmix-static-ip.log 2>&1
''}
${lib.optionalString (dataDisk != null && !configMedium) ''
:: Ensures D: is assigned on every boot -- the image ships without a :: Ensures D: is assigned on every boot -- the image ships without a
:: persisted letter for the data disk -- and heals a profile that went :: persisted letter for the data disk -- and heals a profile that went
:: temporary before D: was ready. Onstart / SYSTEM, like the address task. :: temporary before D: was ready. Onstart / SYSTEM, like the address task.
:: configMedium does not need this: the letter persists via MountedDevices
:: in the overlay after the first boot, so there is nothing to re-assert.
schtasks /create /tn "vmix-data-profile" /tr "C:\vmix-data-profile.cmd" /sc onstart /ru SYSTEM /rl HIGHEST /f > nul 2>&1 schtasks /create /tn "vmix-data-profile" /tr "C:\vmix-data-profile.cmd" /sc onstart /ru SYSTEM /rl HIGHEST /f > nul 2>&1
''} ''}
@ -727,7 +769,7 @@ in {
{ source = loadConfigScript; dest = "/vmix-load-config.cmd"; } { source = loadConfigScript; dest = "/vmix-load-config.cmd"; }
{ source = applyConfigScript; dest = "/vmix-apply-config.ps1"; } { source = applyConfigScript; dest = "/vmix-apply-config.ps1"; }
{ source = createUserScript; dest = "/vmix-create-user.ps1"; } { source = createUserScript; dest = "/vmix-create-user.ps1"; }
{ source = userCleanupScript; dest = "/vmix-user-cleanup.cmd"; } { source = finalizeScript; dest = "/vmix-finalize.cmd"; }
] ]
++ lib.optionals (staticIP != null || configMedium) [ ++ lib.optionals (staticIP != null || configMedium) [
{ source = staticIPScript; dest = "/vmix-static-ip.cmd"; } { source = staticIPScript; dest = "/vmix-static-ip.cmd"; }