13 KiB
SilverMetal WinPE Pre-Config Collector
Status: design — 2026-06-10. Approved in brainstorming. Adds a branded WinPE collector that runs before Windows Setup, captures identity + install-shaping choices, generates the answer file so Setup creates the real account natively (eliminating the
sm-bootstrapaccount + its teardown), and hands the rest to a simplified run-once-then-persist first-boot toolbox (the existing MAUI Welcome app, trimmed). Builds onwindows/installer/build.ps1,windows/installer/autounattend/, andwindows/welcome/(the MAUI wizard).
1. Goal
Let the user pre-configure the installation before the unattended installer runs. A WinPE collector gathers identity + install-shaping choices, writes them into a generated answer file (native account, computer name, locale, auto-logon) plus a carried-forward config file (flavour, BitLocker PIN, app defaults). Windows Setup then creates the real local administrator itself — so the first-boot app no longer creates an account or removes a bootstrap user, and becomes a config/toolbox: it applies the remaining post-install config once (apps, BitLocker), shows the recovery key, then persists as a launchable tool.
2. Decisions (locked in brainstorming)
- Collector scope = identity + install shaping. It owns: the user account
(display name, username, password), computer name, locale/keyboard,
flavour, and the BitLocker choice + PIN. The toolbox owns apps and ongoing
config; hardening runs headless via
SetupComplete.cmd(see the last bullet). - Collector UI = PowerShell + WinForms. WinPE cannot run the MAUI/WebView2 app (no
Edge/Chromium, no modern .NET), so the collector is a separate, branded full-screen
WinForms form.
WinPE-NetFx+WinPE-PowerShell(and dependencies) are added toboot.wim. - Single real admin account. One local Administrators account that the user
defines — replacing both the ephemeral
sm-bootstrapand today's "daily user + SilverOS Admin" split. (The two-account split can return later as a flavour option; out of scope here.) - Toolbox = run-once apply, then persist. On first logon (as the real user) it
auto-runs once, applies the collected config (apps, BitLocker PIN, flavour config),
shows progress + the BitLocker recovery key, reaches Done — then stays installed as a
launchable "SilverMetal" app. The heavy kiosk lockdown (Shell Launcher / Keyboard
Filter /
sm-bootstrapteardown) is dropped. - Handoff = generated answer file + embedded config (Approach 1). Setup keeps owning
the disk. The collector generates the answer file and embeds the non-OS config as
base64 that a
specialize-pass command writes toC:once it exists. - Hardening is canonical in SetupComplete. Hardening runs headless from
SetupComplete.cmdas SYSTEM (as the answer file already intends). The toolbox Apply focuses on apps + BitLocker — no duplicate hardening — so hardening is guaranteed even if the toolbox is closed. (A read-only hardening-status view in the toolbox is SP2.)
3. Architecture / flow
Boot ISO -> WinPE (boot.wim)
winpeshl.ini -> SilverMetal Collector (PowerShell + WinForms)
collects: account . computer name . locale . flavour . BitLocker PIN
-> New-SmAnswerFile -> X:\sm\unattend.generated.xml (embeds preconfig.json base64)
-> setup.exe /unattend:X:\sm\unattend.generated.xml
Windows Setup:
windowsPE pass : wipe disk0 (WillWipeDisk) -> install LTSC index 1 -> locale
specialize pass: decode embedded preconfig.json -> C:\ProgramData\SilverMetal\preconfig.json
oobeSystem pass: create REAL local admin -> AutoLogon(once) -> ComputerName
SetupComplete.cmd (SYSTEM): hardening (canonical) + scrub C:\Windows\Panther\unattend.xml
First logon (real user, auto, once):
Toolbox run-once -> read preconfig -> install apps + enrol BitLocker
-> show recovery key -> Done -> clear PIN -> set "configured" marker
Subsequent launches -> toolbox-home mode
4. Components
4a. WinPE collector (windows/collector/)
- Launch:
boot.wimwinpeshl.inirunsX:\sm\Start-Collector.cmd(instead of the default Setup shell), which callspowershell -ExecutionPolicy Bypass -File X:\sm\Collector.ps1. - UI (
Collector.ps1): a branded full-screen WinForms form (dark theme + SilverMetal logo) with fields:- Account: display name, username, password + confirm.
- Computer name.
- Locale / keyboard (defaults to today's en-GB /
0809:00000809). - Flavour (the existing flavours, read from a small bundled list).
- BitLocker: enable toggle + PIN + confirm (or skip).
- A "Use defaults" fast path that fills sensible defaults so a user can click through.
- Validation (
Test-SmInput.ps1, separated from the WinForms shell so it is unit-testable headless): username rules (non-empty, valid local-account chars, not a reserved name), password present + confirm-match (+ minimum length), PIN numeric + length (>= 6) + confirm-match when BitLocker enabled, computer-name rules (<= 15 chars, valid NetBIOS charset). - Output: calls
New-SmAnswerFilethen launches Setup with the generated file.
4b. Answer-file generator (windows/collector/New-SmAnswerFile.ps1)
Pure function: collected values (a hashtable) -> answer XML string. Mirrors the current
autounattend.xml structure with these differences:
- windowsPE: keep
DiskConfiguration(wipe disk 0, EFI/MSR/Primary) +ImageInstallindex 1; locale from the collector. - oobeSystem: emit one
LocalAccountinAdministrators(the user's account);AutoLogonEnabled withLogonCount=1as that user;ComputerName;Microsoft-Windows-International-Corelocale; the existingOOBEhide-pages block. Nosm-bootstrap. - specialize: a
RunSynchronousCommandthat base64-decodes the embeddedpreconfig.jsontoC:\ProgramData\SilverMetal\preconfig.json(creating the dir). - FirstLogonCommands: launch the toolbox elevated (as today, but as the real user).
- The scrub runs from
SetupComplete.cmd(SYSTEM, end of Setup — guaranteed after account creation): it deletesC:\Windows\Panther\unattend.xmland the cached answer copy.
4c. preconfig.json contract
Written to C:\ProgramData\SilverMetal\preconfig.json:
{
"schemaVersion": 1,
"flavour": "developer",
"bitlocker": { "enable": true, "pin": "246810" },
"apps": { "useFlavourDefaults": true }
}
flavour: the chosen flavour id (drives the toolbox's app defaults + flavour config).bitlocker.pin: consumed by the toolbox to enrol TPM+PIN, then the field is cleared (rewritten withoutpin) once enrolment succeeds.apps:useFlavourDefaults:true(toolbox pre-checks the flavour defaults) or an explicitselected: ["id", ...]list (future: collector app picking — not in SP1; SP1 always emitsuseFlavourDefaults:true).
4d. Simplified first-boot toolbox (windows/welcome/, trimmed)
- Remove: the Account step +
AccountServiceaccount creation;BootstrapServicesm-bootstrapteardown; the heavy kiosk lockdown (Configure-Kiosk.ps1Keyboard Filter / DisableTaskMgr / Shell Launcher path). Branding stays (baked offline; online re-apply unaffected). - Add: a
PreconfigLoaderthat readsC:\ProgramData\SilverMetal\preconfig.jsonand pre-seedsWizardState(flavour, app selection = flavour defaults, BitLocker PIN). On first run (no "configured" marker) the toolbox auto-applies: apps install + BitLocker enrol (using the preconfig PIN) + recovery-key display + Done; then it clears the PIN from preconfig and writes aconfiguredmarker (C:\ProgramData\SilverMetal\configured). - Apply pipeline (trimmed):
apps -> bitlocker -> done. No account step, no hardening (SetupComplete owns it), no teardown. Idempotent / re-runnable. - Persist: the app stays installed with a Start-menu shortcut "SilverMetal". Subsequent launches (marker present) open toolbox-home (a simple landing page; rich ongoing-config surfaces are SP2).
- Run mode selection: marker present -> toolbox-home; marker absent + preconfig present -> first-run auto-apply; neither -> toolbox-home with flavour defaults (fail-open).
4e. build.ps1 changes
- Stage 2b (boot.wim servicing): in addition to forcing legacy Setup, add the WinPE
optional components (
WinPE-NetFx,WinPE-PowerShell, and their depsWinPE-WMI,WinPE-Scripting) viaAdd-WindowsPackage; copywindows/collector/*to the mounted boot.wim at\sm\; writewinpeshl.inito launch\sm\Start-Collector.cmd. - Fallback: keep the static
autounattend.xmlon the ISO as the default answer file. The collector uses it as its template and as the fallback if cancelled. An env/markerSM_UNATTENDED=1(or a build flag) makesStart-Collector.cmdskip the UI and launch Setup with the static default — so CI ISO builds remain non-interactive.
5. Data flow / handoff
Single source of truth = the collector's captured values. From them:
- OS-native fields (account, computer name, locale, auto-logon) -> the generated answer file -> applied by Setup.
- Non-OS config (flavour, BitLocker PIN, app defaults) -> base64-embedded in the
answer file -> written to
C:\ProgramData\SilverMetal\preconfig.jsonin thespecializepass -> read by the toolbox.
No extra partition; Setup still owns disk partitioning (kept simplest + safest).
6. Error handling (must never brick boot)
- Collector cancelled / crashes -> launch Setup with the bundled default answer file (sensible defaults) so install still proceeds. The collector wraps its UI in try/catch and always has a path to Setup.
- Generated XML fails validation (schema/parse check before launch) -> use the default answer file.
preconfig.jsonmissing / corrupt at first boot -> toolbox proceeds with flavour defaults (fail-open, mirroring the app-catalog loader).- winget unavailable -> already handled (graceful skip; PR #19).
- BitLocker enrol fails -> toolbox surfaces it but still reaches Done (apps/onboarding not blocked); PIN retained in preconfig only on failure so a re-run can retry.
7. Security
- Local account only (no Microsoft account / no cloud key escrow) — unchanged.
- Account password transits the generated answer file briefly;
SetupComplete.cmdscrubsC:\Windows\Panther\unattend.xml(and the cached copy) after account creation. - BitLocker PIN transits
preconfig.json; the toolbox clears it after enrolment. - Residual (documented): plaintext secrets exist transiently in WinPE memory and on the freshly-formatted disk until the scrub/clear runs. Acceptable for a local, operator-present install; a future hardening pass could DPAPI-wrap the PIN or prompt it interactively at first logon instead.
8. Testing
- Pester —
New-SmAnswerFile: given inputs, the XML contains the realLocalAccountin Administrators,AutoLogononce as that user, theComputerName, the locale, thespecializebase64-write command, and the Panther scrub; and contains nosm-bootstrap. Base64 round-trips back to the originalpreconfig.json. - Pester —
Test-SmInput: username/password/PIN/computer-name validation rules (valid + each invalid case). - xUnit — toolbox
PreconfigLoader: parses a sample, fails open to flavour defaults on missing/corrupt, clears the PIN after a simulated enrol, honours theconfiguredmarker for run-mode selection. Assert-IsoStructure.ps1: boot.wim containswinpeshl.ini+\sm\Collector.ps1; WinPE-NetFx present (e.g.Get-WindowsPackageshows the package or a marker file); the toolbox payload no longer referencessm-bootstrap.- VM e2e: collector form renders in WinPE -> fill in account/flavour/PIN -> install -> first logon auto-applies (apps skipped if no network, BitLocker per env) -> recovery key shown -> Done -> relaunch shows toolbox-home.
9. Scope / phasing
- SP1 (this spec): WinPE collector (account, flavour, computer name, locale, BitLocker
PIN) + answer-file generation +
preconfig.jsonhandoff + simplified run-once-then-persist toolbox; removesm-bootstrap+ account step + heavy kiosk; hardening canonical in SetupComplete. - SP2 (later): rich toolbox-home — ongoing config surfaces (re-run/extend apps, change settings, view hardening status).
- SP3 (later): install-time disk target / partitioning + BitLocker pre-provisioning on the blank drive (the deferred "maximal" collector scope).
10. Out of scope (SP1)
- App selection inside the collector (SP1 emits
useFlavourDefaults:true; the toolbox remains the place to pick apps). - The two-account (daily + admin) split — SP1 is single admin.
- Disk target / partition UI; BitLocker pre-provision in WinPE.
- DPAPI-wrapping or interactive-at-first-logon secret handling (noted as a future hardening option in §7).