BPRT was stopping after the first RestartRequired=true command (DotNet35).
Test image captured 2026-04-15 showed 3 of 21 PPKG commands ran (PPKG
Version Check, Lock Screen, DotNet35) before provtool exited 0 leaving
Office / Chrome / Tanium / Activate-Windows / Enable-DeviceLockdown /
Hide-SupportUser / 12 more scripts unexecuted. Symptom: criticalChecks
said EntraID NOT joined (wrong -- it was), sessions.json showed a
'LogonIdleTask' session perpetually 'Not started', and the resulting PC
was missing most of its fleet software.
BPRT is the OOBE runtime source -- it expects the OOBE engine to own the
post-DotNet35 reboot + resume. In our post-autounattend context there is
no OOBE engine, so restart-required commands stall the pipeline. PSCmdlet
is the source Install-ProvisioningPackage uses internally and has the
correct resume semantics for post-OOBE application.
The original motivation for BPRT (avoiding the 180s PowerShell timeout)
does not apply because we invoke provtool.exe directly, not via the
Install-ProvisioningPackage cmdlet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
run-enrollment.ps1:
- Enable Provisioning-Diagnostics-Provider/Admin event log before invoking
provtool (was disabled by default; no diagnostics survived early runs).
- After provtool returns, copy C:\ProgramData\Microsoft\Provisioning\*
into C:\Logs\PPKG\ and snapshot HKLM\...\Sessions\* as
provisioning-sessions.json, plus export the Admin event channel to
Provisioning-Diagnostics-Admin.evtx. Gives us reviewable state
without relying on provtool's failure-only diagnostic bundle.
- provtool arg order is positional path + /quiet + /source BPRT (verified
against ProvEventLog from the PS cmdlet internal call).
startnet.cmd / startnet-template.cmd:
- Standard-Timeclock sub-type skips the machine-number prompt. Timeclock
PCs do not use a machine number so forcing a prompt wasted tech time
and left MACHINENUM at the 9999 default anyway. Machine sub-type is
unaffected.
Acrobat Reader enforcement:
- playbook/shopfloor-setup/common/ is the cross-PC-type staging dir. Mirrors
CMM/ structure (enforce script + its Install-FromManifest copy + manifest
template + register script).
- Acrobat-Enforce.ps1 runs as SYSTEM on every logon, reads
acrobatSharePath from site-config.common, mounts the SFLD share with
the same HKLM-backed credential lookup CMM-Enforce uses, hands the
acrobat-manifest.json from the share to Install-FromManifest.
- Install-FromManifest extended with Type=CMD so it can invoke vendor-
supplied .cmd wrappers (Install-AcroReader.cmd does a two-step MSI+MSP
install that does not fit MSI/EXE types cleanly). cmd.exe /c wraps it
because UseShellExecute=false cannot launch .cmd directly.
- Register-AcrobatEnforce.ps1 stages scripts to C:\Program Files\GE\Acrobat
and registers "GE Acrobat Enforce" scheduled task. Called from
Run-ShopfloorSetup.ps1 right before the enrollment (PPKG) step so it
applies to every PC type, not just CMM.
- acrobat-manifest.template.json is the repo reference; the authoritative
copy lives on the SFLD share at
\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\common\acrobat\
Bumping Acrobat updates = drop new MSP on share, bump DetectionValue in
manifest; enforcer catches every PC on next logon.
- site-config.json: add "common": { "acrobatSharePath": ... }. Uses a
new top-level block rather than a PC-type-specific one since Acrobat
applies everywhere.
Initial install still happens via the preinstall flow
(Install-AcroReader.cmd during WinPE). The enforcer is the ongoing-
updates side; on a freshly-imaged PC detection passes and it no-ops.
Also in this commit:
- run-enrollment.ps1: provtool.exe argument syntax fix. First test
returned 0x80004005 E_FAIL in 1s because /ppkg: and /log: are not
valid provtool flags; the cmdlet's internal call used positional
path + /quiet + /source. Switched to that syntax.
Observed today on E8FHGDB4: Install-ProvisioningPackage timed out after
the PowerShell cmdlet's hardcoded 180s limit on a 7.6 GB GCCH v4.10
PPKG. The catch-block fell through to Add-ProvisioningPackage, which
returned "success" but the PPKG diagnostic bundle showed the child
provtool.exe was called with empty packagePathsToAdd (session created,
State=Not started, RebootCount=0). The PC was named, OOBE-completed,
and BPRT apps ran, but the bulk enrollment never applied - PC was not
Entra-joined.
Microsoft Docs GitHub issue 502 confirms the 180s cmdlet timeout is
hardcoded with no configuration option. Quest KB 4376269 suggests
rebuilding the PPKG with the latest Windows Configuration Designer,
but that is upstream and not under our control per PPKG.
Switch to Start-Process -FilePath provtool.exe -Wait. The wait is on
the actual child process, no caller-side timeout. provtool.exe is
what the cmdlet was invoking anyway; we just bypass the wrapper that
imposes the limit.
Sources:
https://support.quest.com/on-demand-migration/kb/4376269https://github.com/MicrosoftDocs/windows-powershell-docs/issues/502https://learn.microsoft.com/en-us/windows/configuration/provisioning-packages/provisioning-apply-package
Workstation reorganization:
- All build/deploy/helper scripts moved into scripts/ (paths updated to use
REPO_ROOT instead of SCRIPT_DIR so they resolve sibling dirs from the new
depth)
- New config/ directory placeholder for site-specific overrides
- Removed stale: mok-keys/, test-vm.sh, test-lab.sh, setup-guide-original.txt,
unattend/ (duplicate of moved playbook/FlatUnattendW10.xml)
- README.md and SETUP.md structure listings updated, dead "Testing with KVM"
section removed
- .claude/ gitignored
Enrollment share internal taxonomy (forward-looking; existing servers
unaffected since they keep their current boot.wim with flat paths):
- Single SMB share kept (WinPE only mounts one Y: drive), but content now
organised into ppkgs/, scripts/, config/, shopfloor-setup/, pre-install/{bios,
installers}, installers-post/cmm/, blancco/, logs/
- README.md deployed to share root explaining each subdir
- New playbook tasks deploy site-config.json + wait-for-internet.ps1 +
migrate-to-wifi.ps1 explicitly (were ad-hoc on legacy servers)
- BIOS subdir moved into pre-install/bios/, preinstall/ renamed to pre-install/
- startnet.cmd + startnet-template.cmd updated with new Y:\subdir\ paths
- Bumped GCCH PPKG references v4.9 -> v4.10
Blancco USB-build fixes (so next fresh USB install boots Blancco end-to-end
without the manual fixup we did against GOLD):
- grub-blancco.cfg: kernel/initrd switched HTTP -> TFTP (GRUB's HTTP module
times out on multi-MB files); added modprobe.blacklist=iwlwifi,iwlmvm,btusb
(WiFi drivers hang udev on Intel business PCs)
- grubx64.efi rebuilt from updated cfg
- Playbook task added to create /srv/tftp/blancco/ symlinks pointing at the
HTTP-served binaries
run-enrollment.ps1: OOBEComplete is now set AFTER PPKG install (Win11 22H2+
hangs indefinitely if OOBEComplete is set before the bulk-enrollment PPKG runs).
Also includes deploy-bios.sh / pull-bios.sh / busybox-static / models.txt
that were sitting untracked at the repo root.
Install-ProvisioningPackage triggers an immediate reboot that kills
run-enrollment.ps1 before it can register the sync_intune task or do
any post-install work. BPRT app installs happen on the NEXT boot, not
before the reboot.
Fix: move sync task registration into Run-ShopfloorSetup.ps1, executed
BEFORE calling run-enrollment.ps1. The task is safely registered while
we still have control. Then enrollment installs the PPKG and lets it
reboot. After reboot, BPRT finishes in background, sync task fires at
logon, monitors Intune enrollment (which is independent of BPRT).
Run-ShopfloorSetup.ps1:
- Registers "Shopfloor Intune Sync" @logon task after desktop tool
copies but BEFORE enrollment
- Flushes transcript before calling enrollment (since PPKG reboot
will kill us, ensures log is complete)
- Enrollment is the absolute last call
run-enrollment.ps1:
- Stripped to essentials: find PPKG, rename computer, set OOBE,
Install-ProvisioningPackage
- No BPRT polling (irrelevant - happens after reboot)
- No task registration (already done by caller)
- No shutdown call (PPKG handles it)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Em dashes (U+2014) and arrows (U+2192) break PowerShell 5.1 on
Windows when the file has no UTF-8 BOM -- byte 0x94 gets read as
a right double quote in Windows-1252, silently closing strings
mid-parse. This caused run-enrollment.ps1 to fail on PXE-imaged
machines with "string is missing the terminator" at line 113.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Install-ProvisioningPackage is async — it queues the provisioning engine
and returns immediately. The actual BPRT app installs (Chrome, Office,
Tanium, CyberArk, etc.) run in the background. Without waiting, the
PPKG reboot fires while installs are still in progress, leaving apps
partially installed.
Fix: poll for C:\Logs\BPRT\Remove Staging Locations\Log.txt — the last
BPRT step. When that file exists, all provisioning steps have completed.
Polls every 10 seconds for up to 15 minutes (Office install can be slow).
Progress logged every 30 seconds showing which steps have finished.
If the timeout fires (15 min), logs a warning and proceeds — the SYSTEM
logon task from 06-OrganizeDesktop.ps1 provides self-healing on the next
boot for anything that was incomplete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the single-session "cancel PPKG reboot and cram everything into
one autologon" flow with a staged chain where each reboot advances to the
next step automatically. The technician touches the keyboard 3 times total
(UNPLUG prompt, Y to reboot, Configure-PC selections).
New Stage-Dispatcher.ps1:
Reads C:\Enrollment\setup-stage.txt and chains through:
shopfloor-setup -> sync-intune -> configure-pc
Each stage re-registers HKLM RunOnce so the dispatcher fires again on
the next logon. Stage file is deleted when the chain completes.
Transcript logged to C:\Logs\SFLD\stage-dispatcher.log.
Stage "shopfloor-setup": runs Run-ShopfloorSetup.ps1 (which reboots via
shutdown /r /t 10). Dispatcher advances stage to sync-intune in the
~10 second window before the machine goes down, re-registers RunOnce.
Stage "sync-intune": launches Monitor-IntuneProgress.ps1 -Unattended.
Exit 2 (pre-reboot done, user confirmed): dispatcher re-registers
RunOnce and initiates shutdown /r /t 5. Stage stays at sync-intune so
the monitor picks up post-reboot state on next boot.
Exit 0 (post-reboot install complete): dispatcher chains directly to
Configure-PC.ps1 in the same session, then deletes the stage file.
Stage "configure-pc": runs Configure-PC.ps1 and deletes the stage file.
Fallback entry point if the post-reboot chain was interrupted.
Modified run-enrollment.ps1:
Removed the shutdown /a that canceled the PPKG reboot. Instead writes
setup-stage.txt = "shopfloor-setup" and registers RunOnce for the
dispatcher. PPKG reboot fires naturally (handles PendingFileRename
operations like Zscaler rename and PPKG self-cleanup). Now tracked in
the git repo at playbook/shopfloor-setup/run-enrollment.ps1.
Modified Monitor-IntuneProgress.ps1:
New -Unattended switch. When set:
Invoke-SetupComplete exits 0 without waiting for keypress.
Invoke-RebootPrompt exits 2 without prompting or rebooting (dispatcher
handles both). Manual sync_intune.bat usage (no flag) unchanged.
RetriggerMinutes bumped from 3 to 5 (user request).
Modified startnet.cmd:
Now also copies Stage-Dispatcher.ps1 from the PXE server to
W:\Enrollment\Stage-Dispatcher.ps1 alongside run-enrollment.ps1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>