PC-DMIS refuses to install without a valid license in /qn mode; its
BA / MSI custom actions ProcessLicensingFromBundle (which spins for
~13 minutes trying to activate against licensing.wilcoxassoc.com)
and IsLicenseDateValid (which errors 1603 when no license file is
present) are the gate. Bypassed by dark-extracting the chained MSIs
from each Burn bundle and pre-patching both custom actions'
InstallExecuteSequence.Condition columns to '0' via Windows
Installer COM SQL UPDATE. The patched MSIs install cleanly with no
license, PCDLRN.exe loads at runtime, hits its own runtime license
check, and shows the normal "no license" dialog. Tech activates via
clmadmin.exe post-imaging and PC-DMIS launches normally.
- playbook/preinstall/preinstall.json: adds VC++ 2010 x64 and VC++
2012 x64 redistributable entries scoped to all PC types. PC-DMIS
links msvcr100.dll / mfc100u.dll (VS 2010) and msvcr110.dll /
mfc110u.dll (VS 2012); without these the exe gets DLL_NOT_FOUND
(0xC0000135) at launch. Win11 ships VC++ 2022 (covers 2015+) but
not 2010/2012, so we ship these from the dark-extracted bundle
payloads. Small (~13 MB combined), inert on PCs that don't need
them, so the filter is "*".
- playbook/shopfloor-setup/CMM/cmm-manifest.json: version 2.0.
Drops the bundle EXEs, installs patched MSIs directly with
properly quoted INSTALLFOLDER / APPLICATIONFOLDER paths (the
earlier "hangs" were caused by Start-Process splitting unquoted
paths on spaces, not actual msiexec hangs). Skips the chained
CLM Tools 1.5/1.7 MSIs - CLM 1.8.73 standalone provides the same
interfaces and PC-DMIS MSIs have no LaunchCondition requiring
Tools 1.5 / 1.7 specifically. Keeps Protect Viewer from the 2019
R2 bundle as a separate entry. CLM 1.8 and goCMM bundles run
unpatched (no install-time license check).
- playbook/sync-cmm.sh: now also includes *.msi files in the
upload set, not just *.exe.
Known caveats: patched MSIs have HashMismatch signatures (expected
- Windows Installer accepts them in /qn mode on locally-cached
machines). Every Hexagon bundle version bump requires re-dark-
extracting and re-patching. Unsupported by Hexagon; do not call
them for install-related issues without reverting to the original
bundles first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CMM imaging pipeline: WinPE-staged bootstrap + on-logon enforcer
against tsgwp00525 share, manifest-driven installer runner shared via
Install-FromManifest.ps1. Installs PC-DMIS 2016/2019 R2, CLM 1.8,
goCMM; enables .NET 3.5 prereq; registers GE CMM Enforce logon task
for ongoing version enforcement.
- Shopfloor serial drivers: StarTech PCIe serial + Prolific PL2303
USB-to-serial via Install-Drivers.cmd wrapper calling pnputil
/add-driver /subdirs /install. Scoped to Standard PCs.
- OpenText extended to CMM/Keyence/Genspect/WaxAndTrace via
preinstall.json PCTypes; Defect Tracker added to CMM profile
desktopApps + taskbarPins.
- Configure-PC startup-item toggle now persists across the logon
sweep via C:\\ProgramData\\GE\\Shopfloor\\startup-overrides.json;
06-OrganizeDesktop Phase 3 respects suppressed items.
- Get-ProfileValue helper added to Shopfloor/lib/Get-PCProfile.ps1;
distinguishes explicit empty array from missing key (fixes Lab
getting Plant Apps in startup because empty array was falsy).
- 06-OrganizeDesktop gains transcript logging at C:\\Logs\\SFLD\\
06-OrganizeDesktop.log and now deletes the stale Shopfloor Intune
Sync task when C:\\Enrollment\\sync-complete.txt is present (task
was registered with Limited principal and couldn't self-unregister).
- startnet.cmd CMM xcopy block (gated on pc-type=CMM) stages the
bundle to W:\\CMM-Install during WinPE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three final optimization batches:
1. Start-Transcript added to 4 scripts that lacked standalone logging:
04-NetworkAndWinRM.ps1, 05-OfficeShortcuts.ps1, 01-eDNC.ps1,
02-MachineNumberACLs.ps1. Each writes to C:\Logs\SFLD\<name>.log
with append mode. Stop-Transcript added before exit points.
2. preinstall.json: Oracle Client PCTypes changed from ["*"] to
["Standard", "CMM", "Genspect", "Keyence", "WaxAndTrace", "Display"].
Lab Workstations don't need Oracle Client (shopfloor data app
dependency). VC++ redists stay at ["*"] (harmless shared deps).
3. Edge profiles added to all remaining PC types in site-config.json:
CMM, Genspect, Keyence, WaxAndTrace, Standard-Timeclock all get the
standard 3-tab setup (Plant Apps + Homepage + Dashboard) with
homepage = tsgwp00524. Display-Lobby and Display-Dashboard get
Shopfloor Dashboard as both homepage and single tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three optimization batches from the pipeline audit:
1. Shared Update-MachineNumber.ps1 helper (lib/)
Extracts duplicated machine-number update logic from Configure-PC.ps1,
Check-MachineNumber.ps1, and Set-MachineNumber.ps1 into a shared
dot-sourceable helper at Shopfloor/lib/Update-MachineNumber.ps1.
Exports:
Get-CurrentMachineNumber → @{ Udc = $string; Ednc = $string }
Update-MachineNumber -NewNumber <n> [-Site <s>] → @{ UdcUpdated; EdncUpdated; Errors }
All three consumers now dot-source the helper instead of duplicating
~50 lines each. Set-MachineNumber.ps1 also migrated from inline
Get-SiteConfig to dot-sourcing Get-PCProfile.ps1 for consistency.
2. Site-config integration for remaining scripts
Setup-OpenText.ps1: exclude lists (profiles + shortcuts) now read from
site-config.json opentext section, falling back to West Jefferson
defaults. Inline Get-SiteConfig since the script runs from
C:\PreInstall\installers\opentext\ (can't dot-source Get-PCProfile).
00-PreInstall-MachineApps.ps1: after parsing preinstall.json, scans
InstallArgs for "West Jefferson" and replaces with site-config
siteName if different. Inline Get-SiteConfig for same reason.
3. Placeholder type-specific directories
Created skeleton 01-Setup-*.ps1 scripts for all PC types so the
directory structure is in place and Run-ShopfloorSetup's type-specific
loop has something to iterate over:
Genspect/01-Setup-Genspect.ps1
Keyence/01-Setup-Keyence.ps1
WaxAndTrace/01-Setup-WaxAndTrace.ps1
Lab/01-Setup-Lab.ps1
Each logs a "no type-specific apps configured yet" banner and exits.
Fill in app installs when details are finalized; for share-based
installs, copy the CMM/01-Setup-CMM.ps1 pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tools:
Configure-PC.bat/.ps1 - Interactive desktop tool for SupportUser to
configure a shopfloor PC after imaging. Two sections:
1. Machine number: if UDC/eDNC are still at placeholder 9999, prompt
to set the real number right now (updates UDC JSON + eDNC registry,
restarts UDC.exe with new args).
2. Auto-startup toggle: pick which apps start at user logon from a
numbered list (UDC, eDNC, Defect Tracker, WJ Shopfloor, Plant Apps).
Creates/removes .lnk files in AllUsers Startup folder. Toggle UI
shows [ON]/[ ] state, safe to re-run anytime. Plant Apps URL
resolved from .url file at runtime with hardcoded fallback to
https://mes-wjefferson.apps.lr.geaerospace.net/run/...
3. Item 6 in the toggle list: register/unregister a "Check Machine
Number" logon task for standard (non-admin) users. When enabled,
the task fires at every logon, checks for 9999, pops an InputBox
if found, updates both apps, then unregisters itself on success.
Check-MachineNumber.ps1 - The logon task script. Runs as the logged-in
user (needs GUI for InputBox), not SYSTEM. Writing to ProgramData + HKLM
is possible because 02-MachineNumberACLs.ps1 pre-grants BUILTIN\Users
write access on the two specific targets during imaging.
02-MachineNumberACLs.ps1 - Standard type-specific script (runs after
01-eDNC.ps1). Opens C:\ProgramData\UDC\udc_settings.json for Users:Modify
and HKLM:\...\GE Aircraft Engines\DNC\General for Users:SetValue. Narrow
scope, not blanket admin.
Execution order fixes in Run-ShopfloorSetup.ps1:
The dispatcher now has two lists: $skipInBaseline (scripts NOT run in the
alphabetical baseline loop) and $runAfterTypeSpecific (scripts run
explicitly after type-specific scripts complete). This fixes the bug where
06/07 ran before 01-eDNC.ps1 installed DnC, so eDNC/NTLARS shortcuts were
silently skipped.
New execution order:
Baseline: 00-PreInstall, 04-NetworkAndWinRM (skipping 05-08 + tools)
Type-specific: 01-eDNC, 02-MachineNumberACLs
Finalization: 06-OrganizeDesktop, 07-TaskbarLayout
06 internally calls 05 (Office shortcuts, Phase 0) and 08 (Edge config,
Phase 4) as sub-phases, so they also benefit from running late. Office
isn't installed until after the first reboot (ppkg streams C2R), so 05
no-ops at imaging time but succeeds when 06's SYSTEM logon task re-runs
it on the second boot. 08 resolves startup-tab URLs from .url files
delivered by DSC (even later); same self-heal via the logon task.
Other fixes in this commit:
- OpenText Setup-OpenText.ps1 Step 4: exclude WJ_Office.lnk, IBM_qks.lnk,
mmcs.lnk desktop shortcuts (matching the Step 3 .hep profile exclusion
from the previous commit). Removes stale copies from prior installs.
- 05-OfficeShortcuts.ps1: widened Office detection to 6 path variants
covering C2R + MSI + Office15/16, with diagnostic output on miss.
- 06-OrganizeDesktop.ps1: removed Phase 3 (desktop-root pin copies for
eDNC/NTLARS) so shortcuts live in Shopfloor Tools only, not duplicated
at root. Emptied $keepAtRoot. Added Phase 0 (call 05) and Phase 4
(call 08). Lazy folder creation + empty-folder cleanup. Scheduled task
now runs as SYSTEM (was BUILTIN\Users with Limited which failed the
admin check). Added NTLARS to 07's taskbar pin list.
- 08-EdgeDefaultBrowser.ps1: Plant Apps URL fallback hardcoded from
device-config.yaml.
- All new scripts have Start-Transcript logging to C:\Logs\SFLD\ with
timestamps and running-as identity.
- Run-ShopfloorSetup.ps1: Start-Transcript + Stop-Transcript wrapping
entire dispatcher run, writes to C:\Logs\SFLD\shopfloor-setup.log.
Configure-PC.bat added to SupportUser desktop copy list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per West Jefferson request, those three connection profiles aren't used
on shopfloor PCs and just clutter the HostExplorer session picker.
They stay in the bundled source tree (dependencies/opentext/Profile/)
for rollback, we just don't copy them into the runtime destinations.
Implementation:
- New optional Exclude list on $contentMap entries
- Copy-HummingbirdContent filters files through Exclude before copying
- Also removes any stale excluded files from the destination up-front,
so a PC that got them from an older install gets cleaned up on
re-deploy (defensive - no production PC has the 15.0.SP1.2 marker
yet so this won't actually fire in practice)
- NO version bump: 15.0.SP1.2 stays, per explicit request. First
imaging run picks up the new logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
OpenText HostExplorer ShopFloor was previously delivered as an Intune
Win32 LOB app that ran the inner OpenTextHostExplorer15x64.msi directly,
which (a) skipped the [Files] section of the WJDT-built Inno Setup
wrapper that deploys profile/keymap/menu/macro files, and (b) deployed
desktop shortcuts pointing at C:\\GE Aerospace\\Hummingbird\\ - a path
HostExplorer doesn't search, so the profile would open from the desktop
shortcut but the keymaps and macros never got picked up.
This commit moves the install to the PXE PreInstall pipeline so it
gets baked into every Standard PC during imaging instead of being
pulled per-device by Intune. The DSC side ships separately as
Setup-OpenText.ps1 + Install-OpenText.ps1 wrapper in the
pxe-images/main/ tree (uploaded to Azure Blob).
preinstall.json: new entry for OpenText pointing at
opentext\\Setup-OpenText.cmd, a tiny launcher in the bundled subtree
that hands off to Setup-OpenText.ps1 (the runner only handles MSI/EXE
types). No DetectionMethod fields - Setup-OpenText.ps1 reads version
from version.txt next to itself and short-circuits via its own
HKLM\\SOFTWARE\\GE\\OpenText\\Installed marker check, so the version
constant lives in exactly one place (version.txt). Trade-off: ~1s
PowerShell launch on every up-to-date runner pass instead of a
zero-cost registry compare, in exchange for never having to bump
the version in multiple places.
sync-preinstall.sh: added dependencies/opentext to TREE_SUBDIRS so
the whole bundle (base MSI + cab + SP1 patch + ShopFloor transform +
profile/accessories/keymap/menu/W10shortcuts content + Setup-OpenText
script and cmd wrapper + version.txt) rides through the existing tar
pipe. Also added OpenText.exe to the legacy-cleanup rm list since the
old flat machineapps/OpenText.exe path is now obsolete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The extracted VC++ 2008/2010/2012/2013/2022 MSIs have a hardcoded
CustomAction CA_LaunchCondition (type 19 = msidbCustomActionTypeError)
whose Target is "To install this product, please run Setup.exe. For
other installation options, see the Installation section of ReadMe.htm."
The CA's Condition row in InstallExecuteSequence is:
NOT( (ADDEPLOY = 1 OR NOVSUI = 1 OR VSEXTUI = 1 OR
ADVERTISED = 1 OR ProductState >= 1) )
So it fires (= aborts the install with that error) unless one of those
sentinel properties is set on the command line. The 2008 MSI uses a
slightly different name (CA_LaunchCondition_5122) and a different set:
NOT( (USING_EXUIH = 1 OR USING_EXUIH_SILENT = 1 OR ProductState >= 1) )
The bootstrappers normally set NOVSUI=1 / USING_EXUIH_SILENT=1 to
identify themselves as non-interactive installers. When we run the
extracted MSI directly via msiexec, the property isn't set, the CA
fires, msiexec returns 1603 with MSI Note 1: 1708, and the install
rolls back.
Fix: pass both properties unconditionally on every VC++ install. MSIs
ignore unknown properties, so one args string works for all of them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Microsoft VC++ bootstrappers (vcredist*_x86.exe) ignore /norestart
and trigger immediate Windows reboots when CRT DLLs are in use, which
in practice is always. We saw this break a live Standard PC imaging
run (installlog showed the manual shutdown -a sequence between runs).
Fix follows the existing 2008 pattern: extract the inner MSIs from
each Burn bundle, run them via msiexec with REBOOT=ReallySuppress
(a hard Windows Installer property the bootstrapper can't override),
and treat exit 3010 as success. Files are now staged per-version
under dependencies/vcredist/<version>/ because each MSI's Media table
hardcodes its CAB filename, so the pairs would otherwise collide.
preinstall.json: 4 EXE entries replaced with 8 MSI entries (Min+Add
for 2012/2013/2022 because each version's Burn bundle ships them
as separate MSIs). 2008 also moved into the same vcredist/2008/
subdir for consistency. ProductCodes verified against the existing
detection paths (the previous "bootstrapper" GUIDs were actually
the Min runtime GUIDs inherited up the chain).
sync-preinstall.sh: now tarballs the dependencies/vcredist/ subtree
to preserve directory structure across the scp+sudo-cp boundary,
flat installers (UDC, Oracle) still copied individually, and the
remote install script now removes the legacy flat vc_red.msi/cab
plus the obsolete vcredist*_x86.exe bootstrappers on every sync.
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>