All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 5m0s
Live e2e: in the sm-bootstrap session the taskbar showed and Win/Start worked. - Keyboard Filter EXEMPTS administrators by default and sm-bootstrap is an admin, so Win/Start/Alt-Tab etc. were never blocked. Set WEKF_Settings DisableKeyboardFilterForAdministrators=false so the filter applies to it. - Auto-hide the taskbar (default-user StuckRects3, inherited by sm-bootstrap) so it doesn't peek over the fullscreen wizard. - TearDownAsync now Disable-LocalUser's sm-bootstrap in-session (immediate) so it's unusable at once; the deferred SYSTEM task still deletes it on next boot (SAM-confirmed the delete works now). Verified: Configure-Kiosk parses under Windows PowerShell 5.1 (ASCII-clean); welcome 29/29. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
4.6 KiB
C#
64 lines
4.6 KiB
C#
namespace SilverOS.Welcome.Core.Apply;
|
|
public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService
|
|
{
|
|
// Lockdown revert is BEST-EFFORT (like TearDownAsync): -EA SilentlyContinue throughout.
|
|
// Don't fail the apply over a missing WMI class / key. Must run BEFORE TearDownAsync.
|
|
public async Task RevertKioskAsync(CancellationToken ct = default)
|
|
{
|
|
// Disable the Keyboard Filter rules so the real end-user's Win key / task-switch /
|
|
// Alt+F4 etc. work again (Explorer is already the shell — nothing to undo there).
|
|
await Ps(
|
|
"$c='root\\\\standardcimv2\\\\embedded';" +
|
|
"foreach($k in @('Win','Win+L','Ctrl+Esc','Ctrl+Win+F','Win+R','Alt+Tab','Ctrl+Shift+Esc','Alt+F4')){" +
|
|
"$p=Get-CimInstance -Namespace $c -ClassName WEKF_PredefinedKey -Filter \"Id='$k'\" -EA SilentlyContinue;" +
|
|
"if($p){$p.Enabled=$false; Set-CimInstance -InputObject $p -EA SilentlyContinue}" +
|
|
"}",
|
|
ct);
|
|
// Revert escape policies set by Configure-Kiosk.ps1.
|
|
await Ps(
|
|
"$s='HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System';" +
|
|
"Remove-ItemProperty $s -Name DisableTaskMgr,DisableLockWorkstation,HideFastUserSwitching -EA SilentlyContinue;" +
|
|
// Restore SECURE UAC for the real end-user (the kiosk auto-approved unsigned elevation).
|
|
"Set-ItemProperty $s -Name ConsentPromptBehaviorAdmin -Value 2 -Type DWord -EA SilentlyContinue;" +
|
|
"Set-ItemProperty $s -Name PromptOnSecureDesktop -Value 1 -Type DWord -EA SilentlyContinue",
|
|
ct);
|
|
}
|
|
|
|
// Teardown is BEST-EFFORT (unlike Account/BitLocker which are strict): the answer file's
|
|
// AutoLogon LogonCount=1 already neutralises auto-logon after the first logon (Windows clears
|
|
// AutoAdminLogon itself), so these Winlogon cleanups must not fail the whole apply. The op that
|
|
// matters — removing the sm-bootstrap account — runs regardless and is tolerant too.
|
|
public async Task TearDownAsync(string bootstrapUser, CancellationToken ct = default)
|
|
{
|
|
const string w = "'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon'";
|
|
await Ps($"Set-ItemProperty -Path {w} -Name AutoAdminLogon -Value '0' -EA SilentlyContinue; " +
|
|
$"Remove-ItemProperty -Path {w} -Name DefaultPassword -EA SilentlyContinue; " +
|
|
$"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 unregisters itself.
|
|
// Disable immediately (in-session, takes effect at once so the account is unusable
|
|
// and shows as disabled), then best-effort delete; the deferred task does the real
|
|
// delete on next boot when it isn't logged on.
|
|
await Ps($"Disable-LocalUser -Name '{u}' -EA SilentlyContinue; 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; " +
|
|
"Unregister-ScheduledTask -TaskName 'SilverMetalBootstrapCleanup' -Confirm:$false -ErrorAction SilentlyContinue";
|
|
var b64 = Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(cleanup));
|
|
// Register-ScheduledTask (not schtasks.exe) — schtasks /tr caps at 261 chars and
|
|
// silently failed with the encoded payload, so the task was never created.
|
|
await Ps("$a=New-ScheduledTaskAction -Execute 'powershell.exe' -Argument " +
|
|
$"'-NoProfile -ExecutionPolicy Bypass -EncodedCommand {b64}'; " +
|
|
"$t=New-ScheduledTaskTrigger -AtStartup; " +
|
|
"$p=New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest; " +
|
|
"Register-ScheduledTask -TaskName 'SilverMetalBootstrapCleanup' -Action $a -Trigger $t -Principal $p -Force | Out-Null", ct);
|
|
}
|
|
private static string Esc(string s) => s.Replace("'", "''");
|
|
private Task Ps(string s, CancellationToken ct) =>
|
|
runner.RunAsync("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -Command \"{s}\"", ct);
|
|
}
|