# 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`:
```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).
