fix(welcome): make bootstrap teardown best-effort (LogonCount=1 already disables auto-logon; cleanup must not fail the apply)
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 4m32s

This commit is contained in:
sysadmin
2026-06-09 12:15:56 +01:00
parent 25b02d20ff
commit bf21eababe
2 changed files with 15 additions and 21 deletions

View File

@@ -1,24 +1,21 @@
namespace SilverOS.Welcome.Core.Apply;
public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService
{
// 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 key = "'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon'";
// DefaultPassword may legitimately be absent → keep its per-cmdlet -EA SilentlyContinue.
await Ps($"Set-ItemProperty {key} -Name AutoAdminLogon -Value 0; " +
$"Remove-ItemProperty {key} -Name DefaultPassword -EA SilentlyContinue", "Disable auto-logon", ct);
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", "Remove bootstrap account", ct);
await Ps($"Remove-LocalUser -Name '{u}' -EA SilentlyContinue", ct);
}
private static string Esc(string s) => s.Replace("'", "''");
// $ErrorActionPreference='Stop' surfaces unexpected hard errors (e.g. a bad registry path);
// the intentional per-cmdlet -EA SilentlyContinue above still overrides it for the known
// best-effort cleanups.
private async Task Ps(string s, string operation, CancellationToken ct)
{
var r = await runner.RunAsync("powershell.exe",
$"-NoProfile -ExecutionPolicy Bypass -Command \"$ErrorActionPreference='Stop'; {s}\"", ct);
r.EnsureSuccess(operation);
}
private Task Ps(string s, CancellationToken ct) =>
runner.RunAsync("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -Command \"{s}\"", ct);
}

View File

@@ -33,12 +33,9 @@ public class ApplyServicesTests
new BitLockerService(Fail().Object).EnableAsync("123456"));
}
[Fact]
public async Task BootstrapService_throws_on_nonzero_exit()
{
await Assert.ThrowsAsync<InvalidOperationException>(() =>
new BootstrapService(Fail().Object).TearDownAsync("sm-bootstrap"));
}
// Note: BootstrapService is intentionally best-effort (teardown cleanups must not fail the
// apply — auto-logon is already neutralised by the answer file's LogonCount=1), so it does
// NOT throw on a non-zero exit.
[Fact]
public async Task AccountService_creates_standard_daily_and_admin()