Files
SilverMetal/windows/docs/superpowers/specs/2026-06-10-winpe-preconfig-collector-design.md
2026-06-10 08:30:48 +01:00

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-bootstrap account + its teardown), and hands the rest to a simplified run-once-then-persist first-boot toolbox (the existing MAUI Welcome app, trimmed). Builds on windows/installer/build.ps1, windows/installer/autounattend/, and windows/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 to boot.wim.
  • Single real admin account. One local Administrators account that the user defines — replacing both the ephemeral sm-bootstrap and 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-bootstrap teardown) 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 to C: once it exists.
  • Hardening is canonical in SetupComplete. Hardening runs headless from SetupComplete.cmd as 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.wim winpeshl.ini runs X:\sm\Start-Collector.cmd (instead of the default Setup shell), which calls powershell -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-SmAnswerFile then 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) + ImageInstall index 1; locale from the collector.
  • oobeSystem: emit one LocalAccount in Administrators (the user's account); AutoLogon Enabled with LogonCount=1 as that user; ComputerName; Microsoft-Windows-International-Core locale; the existing OOBE hide-pages block. No sm-bootstrap.
  • specialize: a RunSynchronousCommand that base64-decodes the embedded preconfig.json to C:\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 deletes C:\Windows\Panther\unattend.xml and 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 without pin) once enrolment succeeds.
  • apps: useFlavourDefaults:true (toolbox pre-checks the flavour defaults) or an explicit selected: ["id", ...] list (future: collector app picking — not in SP1; SP1 always emits useFlavourDefaults:true).

4d. Simplified first-boot toolbox (windows/welcome/, trimmed)

  • Remove: the Account step + AccountService account creation; BootstrapService sm-bootstrap teardown; the heavy kiosk lockdown (Configure-Kiosk.ps1 Keyboard Filter / DisableTaskMgr / Shell Launcher path). Branding stays (baked offline; online re-apply unaffected).
  • Add: a PreconfigLoader that reads C:\ProgramData\SilverMetal\preconfig.json and pre-seeds WizardState (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 a configured marker (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 deps WinPE-WMI, WinPE-Scripting) via Add-WindowsPackage; copy windows/collector/* to the mounted boot.wim at \sm\; write winpeshl.ini to launch \sm\Start-Collector.cmd.
  • Fallback: keep the static autounattend.xml on the ISO as the default answer file. The collector uses it as its template and as the fallback if cancelled. An env/marker SM_UNATTENDED=1 (or a build flag) makes Start-Collector.cmd skip 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.json in the specialize pass -> 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.json missing / 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.cmd scrubs C:\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 real LocalAccount in Administrators, AutoLogon once as that user, the ComputerName, the locale, the specialize base64-write command, and the Panther scrub; and contains no sm-bootstrap. Base64 round-trips back to the original preconfig.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 the configured marker for run-mode selection.
  • Assert-IsoStructure.ps1: boot.wim contains winpeshl.ini + \sm\Collector.ps1; WinPE-NetFx present (e.g. Get-WindowsPackage shows the package or a marker file); the toolbox payload no longer references sm-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.json handoff + simplified run-once-then-persist toolbox; remove sm-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).