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:
parent
b7838f5501
commit
6d4b9155d6
1 changed files with 53 additions and 11 deletions
|
|
@ -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"; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue