Two related fixes from a debugging round on the test PC:
1. PreInstall runner: detection-during-install kill is now opt-in via
"KillAfterDetection: true" on JSON entries that need it. Old behavior
killed any installer as soon as its detection passed - which broke
Oracle: Oracle creates its registry key partway through install,
the runner detected it at the 25s poll, killed msiexec mid-install,
and msiserver was still doing rollback when the next install (VC++
2008) started - so VC++ 2008 hit ERROR_INSTALL_ALREADY_RUNNING
(1618). Only UDC needs the detection-kill (its installer spawns a
hidden WPF window and never exits). Other installers exit cleanly
on their own and shouldn't be killed.
2. Track Setup-OpenText scripts in git. The bundled OpenText install
scripts (Setup-OpenText.ps1, Setup-OpenText.cmd, version.txt) live
at runtime in /home/camp/pxe-images/main/dependencies/opentext/
alongside the binary install files (~106 MB of MSI/CAB/MSP/MST plus
profile content). The binaries stay outside git but the script
logic and version stamp are mirrored into playbook/preinstall/
opentext/ here so git history captures changes to the install
logic and version bumps. README.md explains the workflow.
Latest Setup-OpenText.ps1 includes:
- $SourceDir default moved into script body (PowerShell evaluates
param([string]$X = $PSScriptRoot) defaults at parameter-binding
time, when $PSScriptRoot may not yet be populated, so the
default came out as empty string and Join-Path crashed)
- Logging set up FIRST so any startup error gets captured
- REBOOT=ReallySuppress dropped from both msiexec calls (base MSI
and SP1 patch) - OpenText installs shell extensions that hook
explorer.exe, and Restart Manager closes explorer to replace
the shell DLLs. With REBOOT=ReallySuppress, RM closed explorer
but interpreted the relaunch as a "reboot action" and refused
to do it, leaving the user with no desktop. /norestart on its
own prevents the actual Windows reboot but lets RM cleanly
close-and-relaunch explorer mid-install.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the runner runs a Type:MSI install it injects /L*v <log> and tails
that log on failure to show what actually went wrong. Type:EXE installs
had no equivalent - if Setup-OpenText.cmd or any other EXE wrapper
failed, the installlog just showed "Exit code 1 - FAILED" with no clue
what happened inside.
Adds an optional LogFile field to JSON entries. When present on a
Type:EXE entry, the runner:
- Logs "Installer log: <path>" before launching the installer
- On failure, tails the last 30 lines of that file into the runner
log (same pattern as the MSI verbose log scan)
Wired up on the OpenText entry to point at C:\Logs\PreInstall\Setup-
OpenText.log (which Setup-OpenText.ps1 already writes itself). Other
EXE entries can opt in by adding their own LogFile field.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run-ShopfloorSetup.ps1 line 46-47 does:
Get-ChildItem -Path $baselineDir -Filter "*.ps1" -File | Sort-Object Name
foreach ($script in $scripts) { & $script.FullName }
This picks up EVERY *.ps1 in Shopfloor\ and runs it as a baseline
script. Last commit (66d13d8) put Monitor-IntuneProgress.ps1 in that
same directory, which means the dispatcher was running it as the LAST
baseline script (M sorts after 00/04/05). The monitor is an infinite
poll loop that never returns until the SFLD lifecycle is complete -
so the dispatcher hung there forever, and Standard\01-eDNC.ps1 and
Standard\Set-MachineNumber.ps1 never ran.
Symptoms in the test run:
- 00-PreInstall-MachineApps.ps1 ran (10 installed, 1 OpenText fail)
- 04-NetworkAndWinRM.ps1 ran silently
- 05-OfficeShortcuts.ps1 ran silently
- Monitor-IntuneProgress.ps1 started (Clear-Host + status table) and
hung in its main loop
- eDNC + Set-MachineNumber never ran
Fix: move Monitor-IntuneProgress.ps1 into Shopfloor\lib\ so the
dispatcher's non-recursive Get-ChildItem doesn't see it. Update
sync_intune.bat's MONITOR path to the new location, and add a
comment explaining WHY the monitor lives under lib\ to prevent this
mistake from being repeated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the 3-step pass/fail polling that lived in the .bat with a
PowerShell monitor that renders a full status table for the SFLD
enrollment lifecycle and handles the pre-reboot -> reboot -> post-reboot
transition explicitly.
Three structural problems with the old script:
1. Step 3 ("SFLD - Consume Credentials task exists") fired too early.
The task is created by SetupCredentials.log around 08:52 in the
pre-reboot phase, NOT post-reboot, so passing all 3 gates didn't
actually mean "fully done" - it just meant "credential setup ran".
2. No detection of the pre-reboot -> reboot -> post-reboot transition.
The script never read DSCDeployment.log, so it couldn't tell the
user "you need to reboot now to start the install phase". A device
stuck waiting for reboot was indistinguishable from one still
syncing.
3. No visibility into Phase 4 (per-script wrappers like Install-eDNC,
Install-UDC, Install-VCRedists, Install-OpenText). When something
hung you had to manually grep C:\Logs\SFLD\.
New layout:
sync_intune.bat - thin launcher (~50 lines): self-elevate, invoke
Monitor-IntuneProgress.ps1, branch on exit code
(0 = done / 2 = reboot needed / else = error).
Monitor-IntuneProgress.ps1 - the actual monitor (~340 lines):
- 5-phase status table (Identity / SFLD config / DSC deployment +
install / Custom scripts / Final) updated every 30s via Clear-
Host + redraw, with the QR code anchored at the top.
- Phase 4 auto-discovers custom scripts by parsing DSCInstall.log
for "Downloading script: <name>" lines AND scanning C:\Logs\SFLD\
Install-*.log files - so Display PCs running entirely different
scripts surface their own list automatically without hardcoding.
Statuses: pending / running / done / failed (mtime + tail-based).
- Boot-loop-safe reboot detection via Test-RebootState: only signals
'needed' if DSCDeployment.log was modified AFTER LastBootUpTime.
Once we've rebooted past it, just waits for DSCInstall.log.
- Caches monotonic Phase 1 indicators (AzureAdJoined, IntuneEnrolled,
EnterpriseMgmt task) so dsregcmd /status (slow ~1-2s) only runs
until the flag flips true, not on every poll.
- Triggers Intune sync at startup, re-triggers every 3 minutes (was
every 15 seconds in the old loop, which actively interrupted
in-flight CSP work).
Exit codes consumed by sync_intune.bat:
0 - DSCInstall.log shows "Installation completed successfully"
2 - DSCDeployment.log shows "Deployment completed successfully" AND
the deploy log is newer than LastBootUpTime (= reboot needed)
1 - error
Detection markers (decoded from a captured run at /home/camp/pxe-images/
Logs/ - see comment block at top of Monitor-IntuneProgress.ps1).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Office Click-to-Run installs the binaries when an Office-bearing ppkg
is selected (e.g. GCCH_Prod_SFLD_StdOffice-x86_*) but doesn't create
desktop shortcuts - operators only see Office in the Start Menu's
Microsoft 365 folder. This baseline script fills that gap.
Self-detects Office by EXE existence at C:\\Program Files\\Microsoft
Office\\root\\Office16\\ or the (x86) equivalent. No Office found =
silent no-op, so it's safe to run on every PC type (Display kiosks,
Wax/Trace, Keyence, etc.) without needing a per-type filter.
Creates Excel.lnk / Word.lnk / PowerPoint.lnk in two places:
- C:\\Users\\Public\\Desktop\\ - visible to all users immediately
- C:\\Users\\Default\\AppData\\Roaming\\Microsoft\\Windows\\Start
Menu\\Programs\\ - inherited by every NEW user profile created
on the device (Azure AD operator logons after enrollment)
Numbered 05- so it runs after 00-PreInstall and 04-NetworkAndWinRM
in the Shopfloor baseline sequence. Idempotent - WScript.Shell's
CreateShortcut overwrites existing .lnks each run.
Outlook / OneNote / Access / Publisher intentionally not shortcutted
(scope decision; can be added by extending the $officeApps array).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two related fixes for the desktop helper:
1. Stop hammering the Intune sync trigger every 15 seconds. The old
loop called :do_sync (Start-ScheduledTask on Schedule #3) on every
failed check, which started a fresh CSP pull before the previous
one had time to complete - the Intune engine treats a re-trigger
as "start over" and kills in-flight policy application work, so
nothing ever finished. New cadence: trigger sync once at the start
of each step, then poll every 30 s, only re-trigger every 6 polls
(~3 min). POLL_SECS and RETRIGGER_POLLS are top-of-script knobs.
2. Stop pushing the QR code off the top of the window. The old loop
echoed "Checking again in 15s..." on a new line every iteration,
so after a few minutes the QR code (which contains the device ID
the operator scans) had scrolled out of view. Replaced the per-
iteration echo with a single self-redrawing status line using a
captured CR character (copy /Z trick) and <nul set /p, padded to
clear leftover characters. Important transitions ("Re-triggering
sync...", "[DONE] ...") still print echo. lines so they survive in
the scrollback as permanent history.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three observability fixes that made the VC++ MSI failures actually
debuggable instead of showing "Exit code - FAILED" with an empty
value for every install:
1. Switch from Start-Process -PassThru (without -Wait) to
[System.Diagnostics.Process]::Start() with a ProcessStartInfo.
PowerShell 5.1 has a known bug where Start-Process disposes the
Process object's OS handle when control returns to the script,
so $proc.ExitCode reads as $null even after WaitForExit() - which
was causing every MSI install to be reported as failed regardless
of the actual result.
2. Pass /L*v <log> to msiexec on every MSI install so we get a full
verbose log per app at C:\Logs\PreInstall\msi-<safename>.log.
3. On install failure, scan the verbose log for *meaningful* lines
(Note: 1: <code>, "return value 3", custom action errors, "Failed
to", "Installation failed", common 2xxx error codes) instead of
tailing the last 25 lines, which is rollback/cleanup noise. This
surfaces the actual root-cause line directly in the runner log so
you don't have to dig through C:\Logs to diagnose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace em-dash characters with plain hyphens across the 5 shopfloor
setup scripts (avoids cp1252 mojibake in .bat files and keeps the
PowerShell sources consistent). Also adds [Parameter(Position=1)] to
Write-PreInstallLog so the Level argument can be passed positionally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a local-install pipeline so Standard shopfloor PCs get Oracle, the
VC++ redists (2008-2022), and UDC installed during PXE imaging via Samba
instead of pulling ~215 MB per device from Azure blob over the corporate
WAN. Intune DSC then verifies (already-installed apps are skipped) and
the only Azure traffic on the happy path is ~11 KB of CustomScripts
wrapper polling.
New files:
- playbook/preinstall/preinstall.json — curated app list with PCTypes
filter and per-app detection rules. Install order puts VC++ 2008
LAST so its (formerly) reboot-triggering bootstrapper doesn't kill
the runner mid-loop. (2008 itself now uses extracted vc_red.msi with
REBOOT=ReallySuppress; the reorder is defense in depth.)
- playbook/shopfloor-setup/Shopfloor/00-PreInstall-MachineApps.ps1 —
the runner. Numbered 00- so it runs first in the baseline sequence.
Reads preinstall.json, filters by PCTYPE, polls for completion via
detection check (handles UDC's hung WPF process by killing it once
detection passes), uses synchronous WriteThrough logging that
survives hard reboots, preserves log history across runs.
- playbook/shopfloor-setup/Standard/Set-MachineNumber.{ps1,bat} — desktop
helper for SupportUser. Reads current UDC + eDNC machine numbers,
prompts via VB InputBox, validates digits-only, kills running UDC,
edits both C:\ProgramData\UDC\udc_settings.json and HKLM\…\GE Aircraft
Engines\DNC\General\MachineNo, relaunches UDC. Lets a tech assign a
real machine number to a mass-produced PC without admin/LAPS.
- playbook/sync-preinstall.sh — workstation helper to push installer
binaries from /home/camp/pxe-images/main/ to the live PXE Samba.
Changes:
- playbook/startnet.cmd + startnet-template.cmd — add xcopy to stage
preinstall bundle from Y:\preinstall\ to W:\PreInstall\ during the
WinPE imaging phase, gated on PCTYPE being set.
- playbook/pxe_server_setup.yml — create /srv/samba/enrollment/preinstall
+ installers/ directories and deploy preinstall.json there.
- playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 — bump AutoLogonCount
to 99 at start (defense against any installer triggering an immediate
reboot mid-dispatcher; final line still resets to 2 on successful
completion). Copy Set-MachineNumber.{ps1,bat} to SupportUser desktop
on Standard PCs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete 02-OpenTextCSF.ps1 (CSF profile delivery moved to Intune YAML's
CopyFiles section in main/device-config.yaml — no longer needed at the
PXE/baseline layer)
- Strip MarkZebra install + post-config from 01-eDNC.ps1 (no longer
needed; only eDNC core install + Dnc x86→x64 mirror + Site reg + eMxInfo
deployment remain). Section numbering tightened.
- Add SITESELECTED="West Jefferson" to eDNC msiexec args so the MSI's
site-specific Components (NtLarsWjfRegComp — FTP/FMS/PPDCS hosts +
credentials) actually install. Without it, only the bare Site value was
being set and all the connection details were unconfigured.
- gitignore: blanket-block any **/eMxInfo*.txt from being committed —
the file contains obfuscated eDNC site credentials and must never go
in git. Canonical source lives at /home/camp/pxe-images/main/eMxInfo.txt
outside the repo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bundles QRCoder.dll (184KB, .NET 4.0) to render the Azure AD device
GUID as a scannable QR code in the console when sync_intune.bat runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AutoLogonCount reduced from 2 to 1 in Run-ShopfloorSetup.ps1
- Remove default pinned Start Menu tiles and set blank layout for future users
- Add sync_intune.bat: triggers MDM sync and polls for SFLD group policies
- Update README.md and SETUP.md with current project state (boot chain, new
scripts, samba shares, webapp pages, commit history)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix check-bios.cmd: replace parenthesized if blocks with goto labels
(cmd.exe fails silently with if/else on network-mapped drives)
- Move BIOS check files to winpeapps/_shared/BIOS for reliable SMB access
- Add network wait loop before BIOS check in startnet.cmd
- Show firmware status in WinPE menu header (BIOS_STATUS variable)
- Add BypassNRO registry key to skip OOBE network requirement
- Refactor download-drivers.py with --parallel N flag (ThreadPoolExecutor)
- Set SupportUser AutoLogonCount to 3 in shopfloor unattend
- Add shutdown -a at start + shutdown /r /t 10 at end of Run-ShopfloorSetup.ps1
- Switch download-drivers.py from wget to curl for reliable stall detection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>