All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 4m45s
VM e2e caught a reboot loop: Configure-Kiosk used `Invoke-CimMethod -InputObject $wesl` for SetDefaultShell/SetCustomShell, but WESL_UserSetting exposes STATIC methods and Get-CimInstance returns null — so those calls threw "InputObject is null" while the class-level SetEnabled($true) had already succeeded. Result: Shell Launcher enabled with NO shell configured -> every logon (incl. OOBE defaultuser0) gets a broken shell -> the "Why did my PC restart?" OOBE loop. Fix: call SetEnabled/SetDefaultShell/SetCustomShell all class-level (-Namespace/-ClassName). Setting the DEFAULT shell to explorer.exe is what keeps OOBE/normal logons alive; only sm-bootstrap gets the kiosk launcher. Added GetCustomShell verification + a fail-open rollback (SetEnabled false + RunOnce launch of the Welcome app) so a WMI hiccup can never brick the box again. Same class-level fix applied to BootstrapService.RevertKioskAsync. Found via VM 102 disk logs (silvermetal-firstboot.log + silvermetal-kiosk.log). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
2.9 KiB
C#
44 lines
2.9 KiB
C#
namespace SilverOS.Welcome.Core.Apply;
|
|
public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService
|
|
{
|
|
// Kiosk revert is BEST-EFFORT (like TearDownAsync): -EA SilentlyContinue throughout.
|
|
// If WESL is unavailable the real user still gets Explorer (no custom shell for their
|
|
// SID). Intentional: don't fail the apply over a missing WMI class. Must run BEFORE
|
|
// TearDownAsync so the sm-bootstrap SID still resolves.
|
|
public async Task RevertKioskAsync(CancellationToken ct = default)
|
|
{
|
|
// Remove sm-bootstrap custom shell entry + disable Shell Launcher. WESL_UserSetting
|
|
// methods are STATIC on the class — call class-level (-Namespace/-ClassName), NOT
|
|
// via -InputObject on a Get-CimInstance result (which is always null).
|
|
await Ps(
|
|
"$c='root\\\\standardcimv2\\\\embedded';" +
|
|
"$sid=(New-Object System.Security.Principal.NTAccount('sm-bootstrap')).Translate([System.Security.Principal.SecurityIdentifier]).Value;" +
|
|
"Invoke-CimMethod -Namespace $c -ClassName WESL_UserSetting -MethodName RemoveCustomShell -Arguments @{Sid=$sid} -EA SilentlyContinue | Out-Null;" +
|
|
"Invoke-CimMethod -Namespace $c -ClassName WESL_UserSetting -MethodName SetEnabled -Arguments @{Enabled=$false} -EA SilentlyContinue | Out-Null",
|
|
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",
|
|
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);
|
|
await Ps($"Remove-LocalUser -Name '{u}' -EA SilentlyContinue", 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);
|
|
}
|