diff --git a/docs/ZERO-TOUCH-IMAGING-PROPOSAL.md b/docs/ZERO-TOUCH-IMAGING-PROPOSAL.md index c4a8f05..c9c49fe 100644 --- a/docs/ZERO-TOUCH-IMAGING-PROPOSAL.md +++ b/docs/ZERO-TOUCH-IMAGING-PROPOSAL.md @@ -280,22 +280,32 @@ control once 445 is corporate-reachable.)* ## 7. The PESetup question -### 7.1 It may already be automatic +### 7.1 Autostart does not apply to our media -`AutoStartCountDown = 31` (decompiled), and **`disableauto.json` is absent** from -the media - its *presence* is what disables autostart. Now the observed log: +`AutoStartCountDown = 31` exists in the decompiled settings, and +`disableauto.json` - whose *presence* disables autostart - is absent from the +media. On the surface that suggests a 31-second countdown, and the observed log +is consistent with an operator simply clicking before it fired: ``` 11:30:07.74 'Selection View' started (LTSC warning displayed) 11:30:18.63 Btn 'Next' pressed. ``` -The operator clicked at **11 seconds**. Nobody has waited past 31. +**However:** per the team's operational experience, **LTSC builds cannot +autostart**. The SELECT OS step warns when the media holds an LTSC image +("99% of the time only for ShopFloor. Are you sure you want 'LTSC'?") and that +confirmation is mandatory regardless of the countdown. Our media is +`Windows 11 LTSC 24H2` (`OperatingSystem.json`, build 26100, id 20), so every +image we produce hits it. -**Test first:** one bay, hands off the keyboard, count to 40. If it proceeds, -zero-touch needs no new imaging code at all. +Consequence: **the countdown is not a route to zero-touch for us.** The 40-second +hands-off test is still worth doing once to confirm the behaviour first-hand and +record it - but plan on the answer being "it waits". -### 7.2 If it does hang, re-implementation is tractable +That promotes §7.2 from a contingency to the actual decision. + +### 7.2 Re-implementation - now the likely path, and tractable Nothing in the mechanism is proprietary - only the data, which we already own and already parse. @@ -319,7 +329,8 @@ being a **warning** rather than a failure. **The cost is ownership, not effort.** `Release.json` points at MCL auto-update URLs, so GE expects this media to self-update. Diverging means tracking their format changes indefinitely, plus a likely compliance conversation about the -mandated imaging tool. Hence: run the 40-second test first. +mandated imaging tool. Since the LTSC gate means autostart will not save us +(§7.1), this is a decision to take deliberately rather than a fallback. ### 7.3 What we would keep either way @@ -395,7 +406,9 @@ silently. A clean slate is the moment to fix that. See `docs/OWNERSHIP.md`. Each phase is useful standing alone. No phase requires the next. **Phase 0 - settle the unknowns (hours)** -- The 40-second PESetup autostart test +- Confirm the LTSC autostart gate first-hand (one bay, hands off, 40 seconds) + and record it - expected to wait, per operational experience +- Decide: re-implement the imaging step, or accept one click per build - Decide: one image or three - Decide: join at imaging time, at first power-on, or re-inject @@ -438,7 +451,7 @@ Each phase is useful standing alone. No phase requires the next. | Dual-homing exposes the PXE server | SSH `pxe/pxe`, SMB, TFTP currently `ALLOW IN Anywhere` | Phase 2 gates everything | | BPRT expiry on shelf stock | silent, indistinguishable from "no network yet" | pick a join strategy (§8.2) and add an explicit expiry check | | Selection UI becomes the new single point of correctness | wrong subtype = wrong PC-DMIS, wrong bay config | re-runnable, server-corrected, audited | -| Re-implementing PESetup | ongoing format tracking + compliance | run the autostart test first | +| Re-implementing PESetup | ongoing MCL format tracking + a likely compliance conversation | scope it against "one click per build" - zero-touch is the only thing that needs it | | Big payloads over the corporate network | PC-DMIS, FormTracePak ISOs (~2 GB each), Keyence | GE-Enforce supports `smb` / `http` / `inline` per entry - decide per payload | | Three images drift apart | drift caused the 2026-08-06 outage | prefer one image; `share-drift.py` guards what remains | @@ -448,7 +461,8 @@ Each phase is useful standing alone. No phase requires the next. 1. One image or three? What genuinely differs between them? 2. Join at imaging time, at first power-on, or re-inject on demand? -3. Does PESetup auto-start after 31 seconds? +3. Is the LTSC confirmation truly unskippable? (expected yes - it decides + whether zero-touch requires re-implementing the imaging step) 4. Is replacing the GE-mandated imaging tool acceptable, if it comes to that? 5. Does the selection UI run as SYSTEM (no LAPS password), or as an admin user? 6. Which payloads stay on SMB and which move to HTTPS? @@ -457,12 +471,122 @@ Each phase is useful standing alone. No phase requires the next. --- -## 13. References +## 13. Where everything referenced here lives -- `docs/PESETUP-INTERNALS.md` - decompiled imaging tool behaviour -- `docs/OWNERSHIP.md` - who owns what, with the 2026-08-06 collisions -- `scripts/preflight.py`, `scripts/lint-unattend.py`, - `scripts/lint-driver-catalogue.py`, `scripts/share-drift.py` -- `playbook/scripts/preflight.ps1` - bay-side checks -- shopdb-flask: `plugins/geenforce/seed_display_scope.py` - the pattern this - proposal generalises +### 13.1 This repo (`/home/camp/projects/pxe`) + +| Path | What | +|---|---| +| `docs/PESETUP-INTERNALS.md` | decompiled imaging tool behaviour; the source for every PESetup claim above | +| `docs/OWNERSHIP.md` | who owns what, with the 2026-08-06 collisions | +| `playbook/startnet.cmd` | the WinPE script this proposal thins out - menus, staging, verify, log harvest | +| `playbook/scripts/preflight.ps1` | bay-side checks (secure boot, disk, driver match, media age) | +| `playbook/shopfloor-setup/run-enrollment.ps1` | applies the SFLD package via `provtool` | +| `playbook/shopfloor-setup/Run-ShopfloorSetup.ps1` | post-boot setup; the enrollment-model comment is here | +| `playbook/shopfloor-setup/Fetch-StagingPayload.ps1`, `Verify-And-Heal-Staging.ps1` | first-logon self-heal; read `C:\Enrollment\fetch-source.txt` | +| `playbook/shopfloor-setup/BPRT/Select-PCConfig.ps1` | the pre-seed shim (vendor GUI preserved as `-vendor.ps1` on the share) | +| `playbook/shopfloor-setup/BIOS/{check-bios.cmd,models.txt}` | firmware matching | +| `scripts/preflight.py` | runs every server-side lint in one command | +| `scripts/lint-unattend.py` | schema limits: Path 259, CommandLine 1024, Description 256 | +| `scripts/lint-driver-catalogue.py` | reimplements `GetDriverByModel` | +| `scripts/share-drift.py` | repo vs live share, git-owned vs unreconciled | +| `scripts/build-pctype-media.py` | per-PCTYPE media views (retired by Phase 6) | + +### 13.2 PXE server (`pxe@172.16.9.1`) + +| Path | What | +|---|---| +| `/srv/samba/winpeapps//Deploy/Control/` | `HardwareDriver.json`, `OperatingSystem.json`, `Media.tag`, `Release.json` | +| `/srv/samba/winpeapps//Deploy/FlatUnattendW10.xml` | the answer file that actually boots machines | +| `/srv/samba/winpeapps/_shared/Applications/BPRT/` | the six MCL packages, `Insert-BPRTToPPKG.ps1`, `Start-BulkEnrollOrchestrator.ps1`, `Select-PCConfig*.ps1` | +| `/srv/samba/winpeapps/_shared/BIOS/` | firmware EXEs + `models.txt` (served as the `winpeapps_bios` share) | +| `/srv/samba/winpeapps/_media//` | per-PCTYPE media views | +| `/srv/samba/enrollment/scripts/` | `run-enrollment.ps1`, `preflight.ps1`, `winpe-status-push.ps1` | +| `/srv/samba/enrollment/shopfloor-setup/` | per-PC-type trees, `menu.json`, `_ntlars-backups/` (147 `.reg`) | +| `/srv/samba/enrollment/shopfloor-setup/gea-shopfloor-waxtrace/bay-config.csv` | 15 bays, FTPak versions | +| `/srv/samba/enrollment/installers-post/cmm/cmm-bay-config.csv` | CMM bay -> version, DODA | +| `/srv/samba/enrollment/pre-install/udc-backups/` | per-bay UDC settings | +| `/srv/samba/enrollment/ppkgs/` | the SFLD package (8.2 GB) | +| `/srv/samba/enrollment/imaging-logs//` | harvested imaging logs (added 2026-08-06) | +| `/var/www/html/win11/sources/boot.wim` | the booted WinPE image | +| `/var/log/pxe-imaging/.json` | per-bay stage history | +| `/opt/pxe-webapp/` | Flask webapp (unit `pxe-webapp`, :9009) | +| `/etc/dnsmasq.conf`, `/etc/samba/smb.conf` | the binding config §6.2 depends on | + +### 13.3 shopdb-flask (`/home/camp/projects/shopdb-flask`) + +| Path | What | +|---|---| +| `plugins/geenforce/seed_display_scope.py` | the pattern this proposal generalises - subtype map, server role by IP, sweep | +| `plugins/geenforce/client/` | `ShopdbEnforceClient.psm1`, `Invoke-ShopdbEnforce.ps1` | +| `shopdb/core/api/` | collector endpoint, dashboard-defaults display-role | + +### 13.4 Evidence behind the claims in this document + +| Path | What it shows | +|---|---| +| `/home/camp/pxe-images/now/pesetuplog.txt` | PESetup 4.0.0.20 run - media drive, disking, copy, driver selection, the `Btn 'Next'` timing in §7.1 | +| `/home/camp/pxe-images/now/debug.txt` | the WinPE console for the failed volume-finder run | +| `/home/camp/pxe-images/pxe2/Panther/` | `setupact.log` / `setuperr.log` naming the unattend `Path` failure | +| `/home/camp/pxe-images/debug2/` | the autologon registry state and the sysprep-loop timeline | +| `/home/camp/pxe-images/debug3/Logs/BPRT/` | the 13 package components, `criticalChecks.json`, `packageInfo.json` | +| `/home/camp/pxe-images/debug3/Logs/PPKG/` | the package itself, `provisioning-sessions.json`, the diagnostics evtx | +| `/home/camp/pxe-images/debug3/Logs/enrollment.log` | the double run and the rename race in §9 | +| `/home/camp/pxe-images/debug3/Logs/FilteredReportIP.log` | proves the bay had no route to Entra (§8.2) | + +### 13.5 On-bay locations (for anyone diagnosing one) + +``` +C:\Enrollment\ staged payload, pc-type.txt, display-type.txt, + pc-config.txt, fetch-source.txt, winpe-staging.log +C:\Deploy\Applications\BPRT\ the packages + orchestrator, copied by PESetup +C:\Logs\BPRT\\ per-component package logs (UTF-16) +C:\Logs\BPRT\criticalChecks.json PPKG matched / Entra joined / OOBE execution +C:\Logs\PPKG\ provisioning diagnostics + session state +C:\Logs\SFLD\ DSC deployment, sync_intune, shopfloor-setup +C:\Logs\enrollment.log run-enrollment.ps1 +C:\Windows\Panther\ setupact.log, setuperr.log, unattend.xml, PESetup*.log +C:\Windows\Panther\UnattendGC\ oobeSystem pass +``` + +### 13.6 How to re-derive things + +```bash +# read a file off the share +sshpass -p pxe ssh -o StrictHostKeyChecking=no pxe@172.16.9.1 'cat ' +smbclient //172.16.9.1/enrollment -U pxe-upload%pxe -c 'ls' + +# what is actually inside the booted WinPE +sudo wimextract /var/www/html/win11/sources/boot.wim 1 \ + /Windows/System32/startnet.cmd --dest-dir=/tmp/x +sudo wimdir /var/www/html/win11/sources/boot.wim 1 | grep -i + +# a .ppkg is a WIM - pull out the spec without extracting 8 GB +7z l .ppkg +7z e .ppkg -o "Multivariant/0/customizations.xml" \ + "Multivariant/0/Prov/RunTime/0__Accounts_Azure.provxml" + +# provisioning diagnostics (python-evtx is installed) +python3 -c "from Evtx.Evtx import Evtx; +[print(r.xml()) for r in Evtx('Provisioning-Diagnostics-Admin.evtx').records()]" + +# decompile PESetup - the bundle-extraction recipe is in PESETUP-INTERNALS.md +ilspycmd -t PESetup.dll + +# validate before deploying +./scripts/preflight.py # every server-side lint +./scripts/share-drift.py --diff # repo vs live +``` + +### 13.7 The 2026-08-06 commits this document draws on + +``` +dfa026b lint-driver-catalogue c7b1769 build-pctype-media +049dd7b startnet per-PCTYPE media d5f7abc BIOS 7020 family +385acfe lint-unattend 2b6189f startnet W: wait + honest reporting +d04865d PESetup version + LogonCount b96ff7e retire shopfloor-dashboard route +36be60e run-enrollment stops renaming 8c21282 staging verify + log harvest +d2200e8 preflight (bay + server) 68df59e OWNERSHIP.md + share-drift +9d51c0b purpose menu + 3D kiosk a0aceb4 PXESERVER consolidation +bb08392 download-drivers case fix +```