Files
SilverMetal/windows/iso-builder.md
sysadmin 448de1c570
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (push) Successful in 4m29s
fix(windows/build): revert to prompt boot image (no-prompt caused reinstall loop)
The no-prompt efisys + media-first boot order reboot-loops: every post-copy reboot
re-boots the media before the disk install completes, so it never finishes (symptom:
"no bootable device" after ejecting). Standard efisys.bin (press-any-key) lets reboots
fall through to the installed disk. Legacy-Setup boot.wim patch + /unattend retained
(the real fix). Documented VM-verified result + the residual one-click WinPE language
page in iso-builder.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 23:58:12 +01:00

157 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SilverMetal Enhanced — Windows: ISO Builder
> **Status**: v1 design — 2026-06-08. Implements the productized SKU build for the hardening defined in [`hardening-spec.md`](hardening-spec.md). Reference device: GPD Pocket 4.
This document describes the **reproducible build pipeline** that turns an official Windows 11 IoT Enterprise LTSC ISO plus the SilverMetal config layer into a hardened, branded, UEFI-bootable **custom packed ISO**, with a signed build attestation.
It is bound by the same documents as the hardening spec: [`../docs/threat-model.md`](../docs/threat-model.md), [`../docs/design-principles.md`](../docs/design-principles.md), and [`../docs/trust-model.md`](../docs/trust-model.md).
## 1. Licensing frame (why this is legitimate)
Windows 11 **IoT Enterprise LTSC** is licensed through the OEM / embedded channel, whose explicit purpose is building **customized, locked-down, preinstalled images** on devices a solution-builder ships. Creating a custom packed image and preinstalling it on hardware we sell is the *intended, blessed* use — not a grey area.
| Track | Status under IoT Enterprise LTSC |
|---|---|
| **Preflashed SilverMetal SKU** (we sell the device) | ✅ Build + ship our custom image — licensed per device, intended use |
| **Self-apply free track** (user hardens own device) | ⚠️ No public prebuilt-ISO redistribution. Ships as a **builder** that consumes the user's own official ISO + license — the same `hardening/` modules, applied to their image |
The Microsoft ISO is therefore always an **input**, never committed to this repo. We ship *our layer*; the licensee provides the base.
> **Procurement note (verify before pricing the SKU):** IoT Enterprise LTSC is obtained via authorized OEM distributors; per-device licensing may carry minimum-order / agreement terms. Confirm with the distributor.
## 2. Inputs
All inputs are pinned by hash in [`installer/inputs.manifest.json`](installer/inputs.manifest.json):
| Input | Source | In repo? |
|---|---|---|
| Windows 11 IoT Enterprise LTSC ISO | Licensed (operator/distributor) | No — pinned by SHA-256 |
| GPD Pocket 4 driver pack | GPD (verified source) | `drivers/` or sourced manifest |
| SilverLABS Stack (native Windows builds) | Stack component repos / artifacts | Referenced by version |
| SilverMetal config layer | This repo (`hardening/`, `policies/`, `wdac/`, `debloat/`, `autounattend/`, `oem/`, branding) | Yes |
## 3. Build stages
Orchestrated by [`installer/build.ps1`](installer/build.ps1). Runs on a **Windows host with the Windows ADK** (DISM + `oscdimg`).
1. **Verify input** — assert the source ISO SHA-256 matches the pinned manifest (supply-chain integrity).
2. **Extract** — expand the ISO to a working directory.
2b. **Force legacy Setup** — patch `sources\boot.wim` (index 2) so WinPE launches the legacy installer (see note below). *Required on 24H2/25H2 for a hands-off install.*
3. **Service the WIM offline (DISM)**:
- Select the IoT Enterprise LTSC image index.
- `/Add-Driver` the GPD Pocket 4 driver pack (Strix Point GPU, sensors/auto-rotate, Wi-Fi, fingerprint).
- `/Add-Package` the latest cumulative update (slipstream).
- Trim residual provisioned appx (LTSC is already lean — minimal list in `debloat/`).
- Load offline registry hives → apply the **offline-able baseline** (telemetry floor, service disables) from `policies/`.
- Stage the Stack installers + first-boot payload into the image.
- Commit + unmount.
4. **Inject answer file + first-boot payload**:
- `autounattend.xml` — OOBE automation, **local account** (no MSA), regional settings, BitLocker-ready disk layout.
- `$OEM$\SetupComplete.cmd` — first-boot entry point that runs the online `hardening/` modules and installs the Stack.
5. **Brand** — boot/OOBE wallpaper + computer-name pattern from [`../shared/branding`](../shared/branding).
6. **Repack**`oscdimg` produces a UEFI-bootable ISO (efisys boot image, El Torito).
7. **Attest** — emit output ISO SHA-256, an **SBOM** (every component + version), and a signed build attestation (design-principle #4).
### 3a. Windows 11 24H2/25H2: forcing legacy Setup (critical)
Windows 11 **24H2 (build 26100) and 25H2** ship a **redesigned "ConX" Setup front-end** (`setup.exe → SetupHost.exe → SetupPrep.exe`, launched in WinPE) that **does not honor the `windowsPE` pass** of a root `autounattend.xml`. Symptom (confirmed on our VM test): the new *Select language/keyboard* pages and the OOBE *region* prompt require key presses despite the answer file. **No answer-file element toggles legacy vs new Setup.**
The verified, scriptable fix — applied by `build.ps1` stage 2b — is to point WinPE's shell at the **legacy** installer in `sources\boot.wim` **index 2** (the Setup image; index 1 is WinPE):
```
DISM /Mount-Image sources\boot.wim index 2 → mount
reg load HKLM\SM_BOOT <mount>\Windows\System32\config\SYSTEM
reg add HKLM\SM_BOOT\Setup /v CmdLine /t REG_SZ /d X:\sources\setup.exe /f
reg unload HKLM\SM_BOOT
DISM /Unmount-Image /commit
```
WinPE's winlogon launches whatever `HKLM\SYSTEM\Setup\CmdLine` holds; overriding it to `X:\sources\setup.exe` runs the legacy engine, which consumes all four passes (windowsPE/offlineServicing/specialize/oobeSystem) → fully hands-off. The answer file is placed at **both** the ISO root and `\sources\`.
**Rejected alternatives** (community-tested, unreliable): `winpeshl.ini` `[LaunchApp]` variants and the answer-file-embedded `RunSynchronous` reg trick (the latter only works if the answer file contains *nothing else*, else it reboot-loops WinPE).
**Boot image — use the prompt variant.** Stage 6 uses the standard `efisys.bin` ("press any key to boot…"), *not* `efisys_noprompt.bin`. With a media-first boot order the no-prompt image causes a **reinstall loop** — every post-file-copy reboot re-boots the media before the disk install finishes. The prompt lets reboots fall through to the installed disk. Initial media boot is therefore one keypress (or a firmware boot-menu selection — expected for a USB-installed SKU; in an automated VM boot-test, eject the CD after the file-copy phase or send one key at start).
**Verified end-to-end (VM, 2026-06-08):** with legacy Setup forced + `/unattend`, the answer file drives disk wipe/partition, edition, EULA, and image install **automatically** — Setup jumps straight to "Installing Windows." **Residual:** the single WinPE *language/keyboard* page still needs one click (the `International-Core-WinPE` settings do not reliably suppress it even under legacy Setup on 24H2 — a known, still-open item; everything after that page is hands-off).
**Caveat:** this is ConX-specific behaviour that Microsoft may change in a future cumulative update / ADK refresh — re-verify against the exact base media before each batch. Sources: ElevenForum/NTLite 24H225H2 threads + MS Learn setup-automation docs (the Learn pages predate the redesign and describe only the legacy mechanism). Open question: whether MS later ships a supported way to pre-answer the ConX front-end.
## 4. Where each hardening control is applied
The control domains in [`hardening-spec.md`](hardening-spec.md) split across three layers:
| Layer | Controls | Mechanism |
|---|---|---|
| **Baked offline (in WIM)** | Telemetry floor, service disables, appx trim, drivers, baseline registry policy, Stack staging | DISM + offline hive edits |
| **First-boot (online)** | Local account, VBS/HVCI/Credential Guard, ASR rules, **WDAC (audit)**, firewall, encrypted DNS, Stack + SilverVPN install, scheduled `Verify` task | `SetupComplete.cmd``hardening/` modules |
| **Interactive / firmware — NOT in ISO** | Secure Boot custom-key enrollment, **BitLocker PIN**, BIOS admin password | Operator at provisioning (SKU) or documented user step (self-apply). The ISO *enables* BitLocker and *forces* PIN enrollment; it cannot ship a PIN or touch firmware. |
**The `hardening/` modules are shared.** The same module set is invoked by the ISO's first-boot path *and* by the self-apply / milestone-1 standalone path. Write once, used both ways.
## 5. Reproducibility (honest scope)
Windows image servicing is **not bit-for-bit deterministic** the way the Linux ISO pipeline is (WIM internal timestamps, servicing-stack non-determinism). So for this product, "reproducible" means:
- **Pinned inputs** (every source hashed in the manifest)
- **Recorded tool versions** (ADK, DISM, servicing stack)
- **Output ISO SHA-256 + SBOM** published per build
- **Signed build attestation** linking published artifact ↔ published source + inputs
Bit-identical rebuild is a **stretch goal**, documented as such — we do not claim it (design-principle #2). This is weaker than the Linux line's reproducible-build guarantee, and we say so to buyers.
## 6. Directory layout
```
windows/
├── installer/
│ ├── build.ps1 # pipeline orchestrator
│ ├── inputs.manifest.json # pinned ISO SHA, driver-pack ver, Stack vers, tool vers
│ ├── autounattend/
│ │ └── autounattend.xml # OOBE automation + local account + disk layout
│ ├── oem/
│ │ └── SetupComplete.cmd # first-boot entry → runs hardening/ modules
│ └── README.md
├── hardening/ # §AH PowerShell modules + Verify ← SHARED (ISO + self-apply)
│ ├── 00-provisioning.ps1 # A
│ ├── 01-boot-firmware.ps1 # B (stages keys; firmware steps documented)
│ ├── 02-data-at-rest.ps1 # C (BitLocker TPM+PIN)
│ ├── 03-kernel-credential.ps1# D (VBS/HVCI/CredGuard/DMA)
│ ├── 04-app-control.ps1 # E (WDAC audit, ASR, Defender)
│ ├── 05-network-radios.ps1 # F (firewall, DNS, WiFi-only)
│ ├── 06-physical-lock.ps1 # G (lock-screen, DMA lock, cam/mic)
│ ├── 07-privacy-update.ps1 # H (telemetry trim, update integrity)
│ ├── 08-stack-install.ps1 # SilverLABS Stack
│ └── Verify-SilverMetalWindows.ps1
├── policies/ # GP/ADMX exports + offline .reg/.pol baseline
├── wdac/ # WDAC base policy (XML) + compiled .cip
├── debloat/ # appx removal list, service-disable scripts
├── stack-installer/ # Stack package builders/installers
├── drivers/ # GPD Pocket 4 driver pack (or sourced manifest)
└── tests/ # telemetry-leak test, hardening-baseline test
```
## 7. Milestones
| Milestone | Deliverable | Needs hardware? |
|---|---|---|
| **M0** | This design + scaffolded tree | No |
| **M1** | `autounattend.xml` + `hardening/` modules runnable standalone — hardens the Pocket 4 with no pipeline | Yes (the unit) |
| **M2** | DISM servicing + `oscdimg` repack → first packed ISO, built locally on a Windows + ADK box | Driver pack |
| **M3** | `.gitea/workflows/build-iso-windows.yaml` (Windows runner) + attestation/SBOM + telemetry-leak gate | Windows runner |
| **M4** | Branding, full Stack integration, all verification gates green | Stack Windows builds |
## 8. Build environment & deferred items
- **Build host**: Windows + Windows ADK (DISM + `oscdimg`). A Windows CI runner is required for M3 (mirrors the existing Linux `.gitea/workflows/build-iso-linux.yaml`).
- **Cross-build on Linux** (wimlib + xorriso) is possible but UEFI boot-file assembly is fiddly — **deferred**, Windows-runner path is canonical.
- **Stack Windows builds**: some Stack components are still "Linux MVP" per [`README.md`](README.md); their native Windows builds may lag M4.
- **Driver-pack sourcing**: confirm redistribution terms for the GPD Pocket 4 driver pack, or source at build time from the verified GPD location.
## 9. Open questions
Carried from [`hardening-spec.md`](hardening-spec.md) §8 (resolve on the physical unit), plus builder-specific:
1. Does the IoT Enterprise LTSC media expose the expected image index and OOBE bypass path for `autounattend.xml`?
2. Which Pocket 4 drivers are absent from a vanilla LTSC install and must be injected?
3. Does `oscdimg` produce a Pocket 4-bootable UEFI image with the device's firmware (test on the unit)?