diff --git a/windows/installer/build.ps1 b/windows/installer/build.ps1 index b35922c..31e3f4c 100644 --- a/windows/installer/build.ps1 +++ b/windows/installer/build.ps1 @@ -234,6 +234,11 @@ function Invoke-ServiceWim { Copy-Item (Join-Path $PSScriptRoot 'oem\SetupComplete.cmd') $scripts -Force Copy-Item (Join-Path $PSScriptRoot 'oem\Configure-Kiosk.ps1') $scripts -Force Copy-Item (Join-Path $WindowsDir 'hardening\*') (Join-Path $scripts 'hardening') -Recurse -Force + # Stage the branding module so SetupComplete.cmd can re-apply branding ONLINE + # (Windows resets the offline personalization bake during OOBE). + $brandDest = Join-Path $scripts 'branding' + $null = New-Item -ItemType Directory -Force $brandDest + Copy-Item (Join-Path $WindowsDir 'branding\*') $brandDest -Recurse -Force # Stage Welcome app + flavours while the WIM is still mounted. Copy-WelcomePayload diff --git a/windows/installer/oem/SetupComplete.cmd b/windows/installer/oem/SetupComplete.cmd index 53dfa39..a3a67c4 100644 --- a/windows/installer/oem/SetupComplete.cmd +++ b/windows/installer/oem/SetupComplete.cmd @@ -14,6 +14,14 @@ set HARD=C:\Windows\Setup\Scripts\hardening echo [%DATE% %TIME%] SilverMetal first-boot start >> "%LOG%" +REM Re-apply branding ONLINE (lock screen / wallpaper / OEM / FVE). Windows resets +REM the offline-baked personalization during OOBE, so re-assert it here (post-OOBE, +REM as SYSTEM) where it sticks. Idempotent with the offline bake. +if exist "%~dp0branding\Apply-Branding.ps1" ( + echo [%DATE% %TIME%] re-applying SilverMetal branding (online) >> "%LOG%" + powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0branding\Apply-Branding.ps1" -Mode Online >> "%LOG%" 2>&1 +) + if exist "C:\Program Files\SilverOS\Welcome\SilverOS.Welcome.App.exe" ( echo [%DATE% %TIME%] configuring onboarding kiosk >> "%LOG%" powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0Configure-Kiosk.ps1" >> "%LOG%" 2>&1 diff --git a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs index 64a6922..a31410c 100644 --- a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs +++ b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs @@ -36,7 +36,19 @@ public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService $"Remove-ItemProperty -Path {w} -Name DefaultUserName -EA SilentlyContinue; " + $"Remove-ItemProperty -Path {w} -Name DefaultDomainName -EA SilentlyContinue", ct); var u = Esc(bootstrapUser); + // Best-effort in-session removal (usually no-ops — you can't delete the account + // you're logged in as), THEN defer the real removal to a SYSTEM startup task that + // runs on next boot, when sm-bootstrap is no longer logged on. It removes the + // account + profile, then deletes itself. Encoded command avoids schtasks quoting. await Ps($"Remove-LocalUser -Name '{u}' -EA SilentlyContinue", ct); + var cleanup = + $"Remove-LocalUser -Name '{u}' -ErrorAction SilentlyContinue; " + + $"Get-CimInstance Win32_UserProfile | Where-Object {{ $_.LocalPath -like '*\\{u}' }} | Remove-CimInstance -ErrorAction SilentlyContinue; " + + "schtasks /delete /tn 'SilverMetalBootstrapCleanup' /f"; + var b64 = Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(cleanup)); + await Ps("schtasks /create /tn 'SilverMetalBootstrapCleanup' " + + $"/tr 'powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand {b64}' " + + "/sc onstart /ru SYSTEM /rl HIGHEST /f", ct); } private static string Esc(string s) => s.Replace("'", "''"); private Task Ps(string s, CancellationToken ct) =>