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

12 KiB
Raw Blame History

SilverMetal Enhanced — Windows: ISO Builder

Status: v1 design — 2026-06-08. Implements the productized SKU build for the hardening defined in 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/design-principles.md, and ../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:

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. 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.
  6. Repackoscdimg 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 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.cmdhardening/ 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; 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 §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)?