OLD design: a single 'Check Machine Number' scheduled task ran as the
logged-in user (BUILTIN\Users, Limited) on AtLogOn. It both showed the
InputBox AND tried to update HKLM\SOFTWARE\WOW6432Node\GE Aircraft
Engines\DNC\General + C:\ProgramData\UDC\udc_settings.json. To make
those non-admin writes possible, 02-MachineNumberACLs.ps1 pre-granted
BUILTIN\Users SetValue + Modify on those targets during imaging.
Three problems with that:
1. SECURITY: any logged-in user could overwrite the machine-identity
reg key.
2. FRAGILE: ACL grants raced with eDNC install timing on some bays
(eDNC reg key didn't exist yet when 02-MachineNumberACLs ran;
OpenSubKey returned null, ACL silently skipped, Check-MachineNumber
later failed with PermissionDenied).
3. SILENT-SUCCESS BUG: Update-MachineNumber's Set-ItemProperty calls
lacked -ErrorAction Stop. PermissionDenied is a non-terminating
error in PS5.1, so the try/catch never fired. The script set
$out.EdncUpdated=$true anyway and the dialog reported success
while the reg value stayed at 9999. WJF capture log on FGY07FZ3
shows this exact pattern.
NEW design - two scheduled tasks split by responsibility:
- "Prompt Machine Number" : AtLogOn trigger, BUILTIN\Users (Limited).
Reads current values (read-only). If 9999, shows InputBox. Writes
typed number to C:\Logs\SFLD\machine-number-request.txt. Triggers
SYSTEM Apply via schtasks /run. Polls for result JSON (60s timeout).
Shows result MessageBox with TopMost so it isn't hidden behind
other windows.
- "Apply Machine Number" : on-demand, SYSTEM (Highest). Reads the
request file, calls Update-MachineNumber (full HKLM + ProgramData
access from SYSTEM context). Pulls per-machine NTLARS .reg + UDC
settings JSON + UDC live data from the SFLD share if site-config
has share paths. Writes result JSON. Removes request file.
Unregisters the Prompt task on full success (Prompt itself can't
self-unregister - Limited users can't delete a SYSTEM-owned task).
- Default task SDDL only allows Admins + SYSTEM to read/run a
SYSTEM-owned task. Added BUILTIN\Users GR+GX ACE via COM
SetSecurityDescriptor so the Limited Prompt task can schtasks /run
Apply on demand. They can read + execute it; not modify or delete.
- Update-MachineNumber.ps1 writes now have -ErrorAction Stop so
PermissionDenied actually fires the catch block instead of being
swallowed.
- 02-MachineNumberACLs.ps1 gutted to a no-op (left in place for
Stage-Dispatcher discovery; no longer grants the ACLs). Old bays'
existing grants are harmless since SYSTEM ignores them.
- Register-CheckMachineNumberTask.ps1 now installs both tasks AND
unregisters the legacy 'Check Machine Number' task name on
re-imaging. Run-ShopfloorSetup.ps1's $skipInBaseline list now
includes Prompt-MachineNumber.ps1 + Apply-MachineNumber.ps1 so
they aren't auto-run during the baseline pass (only via the
scheduled tasks).
Smoke tested end-to-end on win11 VM with ShopFloor (Limited) logging in
interactively: AtLogOn trigger fired Prompt, dialog rendered, tech
typed 7777, schtasks /run succeeded (the SDDL fix lets Limited users
trigger SYSTEM tasks), Apply ran as SYSTEM, eDNC reg + machine-number.txt
both updated to 7777, result MessageBox shown, Prompt task auto-
unregistered by Apply's cleanup step. No ACL grants needed on any user.
Apply also re-tested with -ErrorAction Stop confirming non-terminating
PermissionDenied now properly throws into the catch + populates Errors[]
+ flips $out.EdncUpdated to false - so any future write failures will
report honestly instead of silently claiming success.
Bay's ShopFloor user account exists but has never logged in at imaging
time, so its NTUSER.DAT doesn't exist yet and we can't reg-load its
hive to remap source SID -> ShopFloor SID. The in-line restore at
09-Setup Step 3b handles HKLM (controller config, device-map) + files,
but per-user prefs (LouteditS Layout, Page margins, Recent Files, ~2700
rows in a typical WJF capture) get skipped.
Fix: register a SYSTEM-context scheduled task at imaging time that
fires AtLogOn UserId=ShopFloor. When ShopFloor first logs in, Windows
loads their NTUSER.DAT automatically; task fires (running as SYSTEM
so lockdown policies on ShopFloor's user-context don't block HKLM
writes via the same Install script); SID-remap path finds the live
hive and writes prefs into HKEY_USERS\<ShopFloor-sid>. Task writes a
flag file + unregisters itself after one successful run.
Pieces:
- Install-FormtracepakSettings.ps1: new -HKEYUsersOnly switch that
skips the HKLM .reg files + HKLM CSV rows (already restored at
imaging time). Fallback user chain ShopFloor->SupportUser->$USERNAME.
- Schedule-WaxTracePerUserRestore.ps1: registers the task, writes
C:\WaxTrace-Install\Run-WaxTracePerUserRestore.ps1 task action which
invokes Install with -HKEYUsersOnly and self-cleans on success.
- 09-Setup-WaxAndTrace.ps1 Step 3b: in-line restore now uses
-RestoreRegistry -RestoreData -RestoreConfig (HKLM + files now);
calls Schedule-WaxTracePerUserRestore.ps1 to queue HKEY_USERS for
first ShopFloor logon.
- sync-waxtrace.sh: pushes Schedule-WaxTracePerUserRestore.ps1 to
PXE share alongside Install-FormtracepakSettings.ps1.
Smoke tested on win11 VM partially: task registration works, manual
trigger fires + self-unregisters cleanly, flag file lands. Real per-
user SID-remap happens at first ShopFloor logon (can't simulate from
qga without an interactive ShopFloor session).
Picker was still pointed at calibrations/INDEX.csv, which only listed
14 bays that have a per-asset cal ISO ripped. Three bays we just
refreshed into bay-config.csv (WJF00450, WJF00461, WJRP0423) had no
cal-disc entry, so they fell off the menu and tech had to drop to the
free-text prompt to type the asset by hand - felt like a regression.
Two changes:
- select-waxtrace-asset.ps1: prefer bay-config.csv when -IndexPath
points there (now the default). Auto-detect schema by checking for
the ftpak_version column. Display columns become
ASSET / FTPAK / MODEL / USER ID so the tech can confirm bay metadata
at a glance before pressing Enter. Falls back to
calibrations/INDEX.csv if bay-config.csv missing.
- startnet.cmd: invoke the picker with -IndexPath bay-config.csv.
Pushed: both boot.wim copies refreshed via wimupdate, new
select-waxtrace-asset.ps1 deployed to PXE share, new boot.wim landed
at /var/www/html/win11/sources/boot.wim on 172.16.9.1.
bay-config.csv parent-dir copy synced with scripts/bay-config.csv so
resolve-bay-config.ps1 (called from startnet.cmd in WinPE) and the
picker both see the same 17-bay set.
Wires the three pieces that make the per-bay backup restore happen as
part of the PXE imaging pass, no manual post-imaging step required:
1. sync-waxtrace.sh: stage per-asset backup ZIPs from
/home/camp/pxe-images/wt/<asset>/formtracepak_backup_*.zip (newest)
into installers-post/waxtrace/backups/<asset>.zip on the PXE share.
Also pushes scripts/Install-FormtracepakSettings.ps1 alongside the
bootstrap bundle so 09-Setup can call it post-vendor-install.
2. startnet.cmd: after the FTPak ISO cherry-pick, xcopy
Y:\installers-post\waxtrace\backups\%MACHINENUM%.zip to
W:\WaxTrace-Install\backup\%MACHINENUM%.zip. Logs INFO if no per-asset
ZIP exists - 09-Setup will then skip the restore step.
3. 09-Setup-WaxAndTrace.ps1 Step 3b: between cal ISO and OpenText
auto-start steps, look for C:\WaxTrace-Install\backup\<asset>.zip
and invoke Install-FormtracepakSettings.ps1 -BackupPath ...
-RestoreData -RestoreConfig -Force. Registry restore is intentionally
omitted - captured HKLM is overwritten by the vendor MSI install in
Step 2 anyway, and captured HKEY_USERS would land at the source
bay's SID (which doesn't exist on the freshly imaged bay).
bay-config.csv refresh: 17 captured bays with full version/model/
user_id/hw_sn/hw_id/host. Versions stick to the original bay-config.csv
target values where the live binary drifted to a release we don't have
an ISO for (e.g. WJRP2035 live 5.7.0.82 -> imaging targets 6.0).
WJF00450 flagged MISSING_DATA in user_id + hw_id columns so the
imaging path aborts cleanly until the dongle is read.
Smoke tested on win11 VM with WJF00545's real capture: staged the
expected bay-side layout (C:\WaxTrace-Install\Install-FormtracepakSettings.ps1
+ C:\WaxTrace-Install\backup\WJF00545.zip), invoked the resolve +
call path from a simulated Step 3b - 17 files restored cleanly, 0
errors.
WJF00052 / WJF00083 / WJF00084 / WJF00159 produced 0-byte ZIPs on the
2026-05-24 capture pass. Export-FormtracepakInventory was the primary
culprit (Get-Service hang, fixed in d359563), but Backup has the same
root failure mode for the same bays: reg.exe export via Start-Process
-Wait and the recursive Get-ChildItem walk over HKLM:\SOFTWARE\
WOW6432Node\Mitutoyo (95k+ values) both lack timeouts, so a degraded
SCM / antivirus interception / WMI repository on the bay can wedge the
script and the operator kills the .bat - same outcome as the inventory
hang.
Two timeout fences:
- reg.exe export: switch from Start-Process -PassThru -Wait to
[System.Diagnostics.Process]::Start + WaitForExit(300_000). If the
process hasn't exited after 5 minutes, Kill() it and log + count an
error; the .reg file for that one root is skipped but other roots
+ the CSV fallback + the file/data captures continue. Bonus: the
ProcessStartInfo path also gives reliable $proc.ExitCode access; the
Start-Process -PassThru object sometimes returned a stub with
unpopulated ExitCode, producing a false "reg.exe exit for ..."
warning even on successful exports.
- Recursive Get-ChildItem CSV walk: move into a Start-Job + Wait-Job
-Timeout 600 (10 min). If the walk hangs, Stop-Job, log, increment
error count, .reg file remains authoritative for that root.
Also fixed a subtle Start-Job return-shape bug introduced in the same
edit: emitting the rows via the pipeline inside the job + Receive-Job
flattens correctly, whereas `return ,$list` wrapped the whole List in a
single-element array, so the outer foreach was treating the list-of-95k
as a single row. Net effect was Registry Values: 4 instead of 95152 in
the manifest. Verified fixed via a full real-install run on the VM:
95152 values captured cleanly in 22 s, 0 errors.
Net behaviour: the failed-bay re-runs (WJF00052/00083/00084/00159)
should now either produce real ZIPs or print a clear warning naming the
specific reg root that hung, instead of leaving an empty ZIP behind.
WJF00052 / WJF00083 / WJF00084 / WJF00159 hung indefinitely on step [5/5]
during the 2026-05-24 capture pass and the operator killed the .bat,
leaving empty inventory CSVs. The earlier Start-Job + Wait-Job -Timeout 30
guard (commit fce6680) was insufficient: Stop-Job on a Get-Service that's
blocked on a degraded Service Control Manager can itself block in the SCM
call (Sentinel HASP driver service in particular has been observed to
wedge Get-Service for minutes), so the main script never unblocked even
after the 30s timeout fired.
Service state isn't load-bearing for identifying the install on a bay
(version + model + DeviceName come from disk + registry, not the SCM),
so the cleanest fix is to drop the Get-Service block entirely. Get-Process
is fast and stays. Step [5/5] label changed from "Checking running
processes and services" to "Checking running processes".
Smoke tested on win11 VM: full Export against a real v6.213 / AVANT
install completes in ~25 s (was 2.9 s with no install, 25 s now reflects
the legitimate ~95k registry walk that dominates the runtime - not the
service hang).
Installed FormTracePak v6.213 on the win11 VM (picking FORMTRACER Avant
in the dialogs) and probed the resulting registry / disk layout to find
out what evidence a real FormTracePak install actually carries. Two
empirical findings:
1. ACTIVE MODEL lives at
HKLM:\SOFTWARE\WOW6432Node\Mitutoyo\FORMPAK\Config\device map\DeviceName
(string value). For the AVANT install the value is "FORMTRACER Avant";
for the CV/SV/CS controllers the value contains the matching model id.
The Surfpak\FormMes\MachineInfo\Machine\Machine* subtree lists EVERY
supported machine and is NOT the active selection - the previous
heuristic that scanned uninstall-entry DisplayName picked up bogus
WinUSB driver-package entries from "Mitutoyo Corporation" instead.
2. INSTALLED BINARY VERSION is in Formtracepak.exe VersionInfo:
FileVersion=6.2.0.51, ProductVersion=6.2.0.0. This does NOT match the
Mitutoyo MSI release label ("6.213") that bay-config.csv uses. The
uninstall entry's DisplayVersion is empty. So bay-config.csv stays
canonical for the per-asset marketing version; exe FileVersion is a
concrete cross-check.
Backup rewrites:
- Replace the previous one-shot version detection with evidence reading:
bay-config.csv (asset->version+model), Formtracepak.exe VersionInfo,
device-map\DeviceName. The over-broad uninstall-reg regex is gone.
- Normalize DeviceName ("FORMTRACER Avant" / "CV-4500" / "CV-3200" /
"Contracer" / "Surftest") to bay-config notation (AVANT / CV-4500 / ...).
- Emit BayConfigMatch flag - true when bay-config-predicted model agrees
with the device-map\DeviceName on disk. False = drift, tech rechecks
before restoring to a new bay.
- manifest.json now stamps: AssetNumber, FormtracepakVersion, Model,
BayConfigVersion, BayConfigModel, BayConfigSource, InstalledExeVersion,
InstalledExePath, InstalledDeviceName, InstalledModelNormalized,
BayConfigMatch.
HKEY_USERS sweep:
- Wax/Trace bays log in as a per-site user (lg782713sd at WJ today,
ShopFloor post-SFLD-2.0, other accounts at other sites). The previous
HKCU:\ scan only captured the script's running user. Sweep every
loaded HKEY_USERS hive whose SID matches S-1-5-21-* (real user SIDs)
for Software\Mitutoyo / FORMTRACEPAK / FORMPAK / SURFPAK subkeys,
add them to $RegistryRoots. Username-agnostic - works at any site
without changes.
reg.exe export now also accepts the Registry::HKEY_USERS\<sid>\... PSPath
form by stripping the "Registry::" prefix when building the reg.exe
argument (previously emitted "Invalid key name" errors on HKEY_USERS roots).
Smoke tested against a real v6.213 / FORMTRACER Avant install on win11
VM: bay-config lookup matches, exe FileVersion read, device-map
normalized to AVANT, all four .reg files (HKLM, HKLM-WOW6432Node, HKCU,
HKEY_USERS\<interactive-sid>) exported clean, 0 errors.
Restore-side SID translation (HKEY_USERS\<src-sid>\... -> target user's
SID or HKCU on the new bay) is a follow-up. HKLM tree carries the
critical device-map\DeviceName, controller config, and machine settings;
the HKEY_USERS hive captures per-user UI prefs only.
Backup-FormtracepakSettings observed 17 Errors on a real shopfloor PC
(G5PRTW04ESF / WJF00159 capture) - all of the form:
WARNING: Failed to copy ...App.ini: Exception calling ".ctor" with "0"
argument(s): "This implementation is not part of the Windows Platform
FIPS validated cryptographic algorithms."
Cause: Windows FIPS policy is enabled on West Jefferson shopfloor PCs.
The per-file Get-FileHash -Algorithm MD5 call throws a hard .NET exception
that bypasses -ErrorAction SilentlyContinue (the throw is from the MD5
constructor, not the cmdlet's parameter binder). That exception was caught
by the broad try/catch around both Copy-Item + manifest add, producing a
misleading "Failed to copy" message even though Copy-Item already succeeded.
Net effect: files copied fine, but manifest rows were missing for those
files (Install would fall back to its bulk-copy path).
Two fixes:
- Switch the hash algorithm from MD5 to SHA256 in both Backup (manifest
row capture) and Install (Restore-FileItem hash-skip compare). SHA256
is Get-FileHash's default and is FIPS-compliant. Old MD5-hashed backups
remain restorable because Install computes hashes fresh from disk at
restore time and does not read the Hash column from file_manifest.csv.
- Split the broad try/catch in Backup's Copy-ToStaging into two
try/catches: the first wraps only Copy-Item (real copy failure -> Errors
counter + skip the file), the second wraps only Get-FileHash (hash
failure -> log warning, manifest row gets a null Hash and is still
recorded). A hash failure no longer pretends the copy failed.
- Install's hash compare is wrapped in try/catch too so a hash exception
falls through to overwrite-mode rather than crashing the restore.
Smoke tested on win11 VM: SHA256 round-trip works (64-char hashes in
file_manifest.csv), Backup reports 0 Errors, Install hash-skip path
correctly skips Identical files on second-run idempotency check.
Two operator-driven fixes.
1. Backup target moves from S:\2 WJ Scans Record Retention\backup\waxtrace
to S:\DT\Shopfloor\backup\waxandtrace per the canonical SFLD layout.
Backup creates the per-asset folder if missing; Install reads from the
same path by default.
2. Export-FormtracepakInventory hung on step [5/5] when run on a shopfloor
PC. The original `Get-Service | Where-Object { DisplayName -match ... }`
pattern materializes every service via the Service Control Manager + post
filters in PowerShell, which can block indefinitely when any single
service (Sentinel HASP driver, GE-Enforce agent, etc.) is in a degraded
state. Two-part fix:
- Switch to Get-Service -DisplayName 'Mitutoyo*','*FORMTRACEPAK*',...
so the SCM only materializes matching services (server-side wildcard
filter, faster + lower blast radius).
- Wrap the enumeration in Start-Job + Wait-Job -Timeout 30 so a
degraded SCM aborts gracefully with a warning rather than wedging
the whole inventory pass.
Smoke tested on win11 VM: full Export run with the new code completes in
2.9 s and emits the inventory CSV correctly.
Tech ran Export-FormtracepakInventory.ps1 from S:\DT\shopfloor\scripts\
waxandtrace\ and the picker fired correctly but Export-Csv failed with
'Cannot bind argument to parameter Path because it is an empty string'.
Root cause: $OutputPath defaulted to $PSScriptRoot and $PSScriptRoot came
through empty in that invocation path (suspected ISE / IEX-style host or
remote wrapper). On a [string] param, $null/empty default coerces to ''
and Join-Path then errors.
Fix in all three triad scripts: resolve a local $scriptDir via a fallback
chain ($PSScriptRoot -> $PSCommandPath -> Get-Location), and use that
instead of $PSScriptRoot for sibling lookups (Select-WaxtraceAsset.ps1,
bay-config.csv).
Export additionally:
- Drops the $OutputPath = $PSScriptRoot param default in favor of the
same fallback chain.
- Tests / creates $OutputPath BEFORE the 90k-item registry scan so a bad
output dir surfaces immediately instead of after a long scan.
Smoke tested on win11 VM: explicit -OutputPath '' now resolves to a
writable directory and the CSV writes successfully.
Two operator-UX improvements for the Backup / Export / Install triad.
1. Backup target moves from \\tsgwp00525\...\formtracepac to S:\2 WJ Scans
Record Retention\backup\waxtrace\<asset>\. S: is mapped at shopfloor
imaging time and stays mapped post-categorization, so the same default
path works whether the operator runs the backup on an old bay (manual
pre-image capture) or a freshly imaged one. The destination directory
is created if missing.
2. New Select-WaxtraceAsset.ps1 - arrow-key bay picker patterned after
the WinPE select-waxtrace-asset.ps1. Reads bay-config.csv (sibling
file), shows asset_tag + ftpak_version + model + user_id per row, and
returns the selected asset_tag via stdout. Falls back to a manual
entry prompt if the CSV is missing or the operator picks "Other".
Backup / Export / Install now invoke the picker when interactive AND
bay-config.csv is alongside the script. Non-interactive paths
(qga / SYSTEM / scheduled task) keep silently defaulting to
COMPUTERNAME so unattended runs are unchanged.
Export gained an -AssetNumber parameter and stamps it into the output
CSV filename so multiple inventories from the same host stay
distinguishable when the operator is auditing several bays in a row.
bay-config.csv is copied into the scripts\ dir so the picker has a
source of truth that ships next to the scripts (and into pxe-images
for tech distribution).
Smoke tested on win11 VM: all four PS1 parse-clean, non-interactive
backup path still produces a valid ZIP (silent COMPUTERNAME default),
picker handles missing-CSV gracefully (manual-entry fallback). The
arrow-key UX itself is operator-verifiable only on a real terminal.
Three fixes in Backup / Export / Install, validated end-to-end on the win11 VM
against a seeded HKCU\SOFTWARE\Mitutoyo\Formtracepak key carrying all five
registry value types (String, DWord, ExpandString, MultiString, Binary).
1. Registry corruption on REG_BINARY / REG_MULTI_SZ restore
Backup wrote those values to registry_values.csv via [string]$val, which
lossily coerces a byte[] to "System.Byte[]" and a string[] to a
space-joined scalar. Install's CSV restore loop runs AFTER the .reg file
import (which is lossless), so the CSV pass overwrites the good values
with corrupted strings. Two-part fix:
- Backup: skip Binary / MultiString / None / Unknown when writing the CSV.
Only String, ExpandString, DWord, QWord roundtrip cleanly through
New-ItemProperty -PropertyType, so capture only those. The .reg file
remains authoritative for the rest.
- Install: defensive filter on the CSV restore loop that skips any row
whose Type is not in {String, ExpandString, DWord, QWord}. This catches
legacy CSVs already on the share that were taken before this fix.
2. v6.213 vendor install path not scanned / not restored to
The per-bay FormTracePak install (commit 54dddaa) lands under
C:\Program Files (x86)\MitutoyoApp\Formtracepak, but the search-path
lists in Backup + Export only covered C:\...\Mitutoyo (no MitutoyoApp).
Result: a backup taken on a freshly imaged v6.213 bay produced Config
Files = 0 because the script never looked at the actual install dir.
Added MitutoyoApp (x86 + native ProgramFiles) ahead of the legacy
paths in all three scripts.
3. Install $DefaultAppTargets fallback didn't include MitutoyoApp either,
so a restore from an OLDER bay (source path C:\Mitutoyo\...) onto a
freshly imaged v6.213 bay would fall back to ProgramFiles\Mitutoyo
(does not exist), miss the MitutoyoApp\Formtracepak tree, and write
the restored files into the first existing legacy path. Added the
MitutoyoApp entries at the top of the ordered fallback table.
Smoke tested on win11 VM: backup of all 5 reg types, then corrupt every
value, then Install -RestoreAll restores all 5 byte-exact (incl. REG_BINARY
DE-AD-BE-EF-CA-FE-BA-BE-01-02-03-04 and REG_MULTI_SZ alpha.smp/beta.smp/
gamma.smp). Verified legacy poison-CSV path triggers the defensive filter
and the .reg-imported values survive untouched. -DryRun confirmed
non-mutating. Idempotency confirmed via hash-skip.
Bays span 7 FormTracePak versions (5.510 - 6.213) and 3 sub-versions
(AVANT / CV-4500 / CV-3200), each with a unique licensing USER ID. Previously
all bays got v6.213 with no model/USER hint to the tech.
- bay-config.csv: 15 rows mapping asset_tag to ftpak_version + model + user_id.
- resolve-bay-config.ps1: WinPE-runnable resolver. Looks up the asset and
writes version.txt / model.txt / userid.txt / bay-info.txt under
W:\Enrollment\waxtrace\.
- startnet.cmd: xcopy WaxTrace bundle minus formtracepak\, invoke the
resolver with %MACHINENUM%, then cherry-pick only the matching
FORMTRACEPAK-V<ver>.iso (~2 GB local vs ~12 GB if all were staged).
- 09-Setup-WaxAndTrace.ps1: read the per-bay files, mount the right ISO,
drop <asset>-FTPak-install-info.txt on SupportUser's desktop, and print
a banner with MODEL + USER ID so the tech has them top-of-mind when
Setup.exe dialogs come up.
- sync-waxtrace.sh: loop over all FORMTRACEPAK-V*.iso instead of hard-coding
v6.213; also push bay-config.csv + resolve-bay-config.ps1 to the share.
- Drop -Physical + MediaType filter from Get-NetAdapter; some OEM driver
stacks report HardwareInterface=False or localize MediaType, hiding the
Realtek controller NIC from the previous query.
- Refine corp-vs-controller classification: skip only if a gateway is set
AND it's not 192.168.1.x, OR if the IP looks corp (10.x / 172.16-31.x).
Keep candidates that are unconfigured, link-local, or 192.168.x.
- Disable DHCP in both PersistentStore and ActiveStore before New-NetIPAddress
to avoid "Inconsistent parameters PolicyStore PersistentStore and Dhcp
enabled" failures.
- Fall back to netsh interface ip set address when the PS cmdlets still
fight each other; netsh writes both stores cleanly.
Pre-2026-05 Keyence images didn't write pc-subtype.txt via startnet.cmd. Without
a subtype the share manifest's per-model PCTypes gate falls back to the default
(VR-6000), causing the wrong model to install on VR-3000 / VR-5000 boxes.
Detect the installed VR-3000/5000/6000 by its uninstall ProductCode and
persist the subtype so subsequent GE-Enforce cycles + the share manifest gate
route correctly.
The direct-copy bypass added earlier was never firing - Get-ChildItem -Filter
uses Win32 filename filtering and does NOT honor PowerShell wildcards or
character classes. The previous detection filter '*[0-9] _*.txt' matched
literal bracket-zero-through-nine text, which never appears in any
filename. $hasBrokenFilenames was therefore always False, and every
218-378-13 series cal apply fell through to the vendor setup.exe which
crashes with System.ArgumentException (exit -532462766).
Confirmed via debug-waxtrace-cal.ps1 log on WJF00159: section 6 reports
the on-disk script has the direct-copy fix, section 7 shows the actual
runtime log line 'running cal Setup.exe' followed by exit -532462766.
The "fix" was never executing because the gate was broken.
Replace -Filter with Get-ChildItem -File + Where-Object regex match on
' _\d+\.txt$' which catches the actual buggy filename pattern (space-
underscore-digits-.txt at end of name) regardless of probe series.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removed the placeholder DODA entry from Applications so a bay imaged
with the 'With DODA' submenu choice today does not log a 'Installer
not found: DODA-PLACEHOLDER.exe' error per cycle. Wiring is otherwise
unchanged: startnet.cmd still offers the With-DODA submenu, pc-subtype.txt
is still written as 'doda', and 09-Setup-CMM.ps1 still passes PCSubType
through to Install-FromManifest. When the DODA installer is sourced,
move the entry from _pending_doda_entry back into Applications (engine
ignores any top-level field other than Version + Applications).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Carries over the lessons learned from wax/trace + Keyence imaging today
and threads the same pattern through the CMM path.
09-Setup-CMM.ps1:
- Pass PCType + PCSubType to Install-FromManifest so the manifest's
per-entry PCTypes filter is honored. Without this every entry runs
regardless of bay variant - the same bug Keyence had before per-model
gating was added.
- Move bootstrap cleanup to a conditional that only deletes
C:\CMM-Install once every (filter-applicable) manifest entry detects
as installed. If a Hexagon installer forces an unplanned reboot
mid-install, the new Run-ShopfloorSetup self-resume RunOnce fires on
the next auto-login; the staging dir needs to still be on disk for
the re-run to recover. Logs "retained ... not all entries installed
yet - will retry on next self-resumed run" when partial.
cmm-manifest.json:
- Drop exact DetectionValue from PC-DMIS 2016, PC-DMIS 2019 R2, CLM
1.8.73, and goCMM. Detection is now uninstall-key presence only, so
a Hexagon security patch that bumps the DisplayVersion does not
trigger a re-install loop with exit 1638 every GE-Enforce cycle.
Bumping the installer in apps/ is the upgrade path - manifest engine
detection should not also be a version drift catcher for vendor MSIs
whose backward-compat is established by the vendor.
- Specific to goCMM: the installer filename version (1.1.6718.31289)
does not match what the installer registers under its uninstall key.
Dropping DetectionValue silences the false-mismatch loop the prior
version would have triggered.
- Add DODA placeholder entry gated to PCTypes=["cmm-doda"]. Real
Installer filename, args, and DetectionPath still TODO once the
DODA binary is sourced + dropped at installers-post/cmm/.
startnet.cmd:
- Add :cmm_submenu after the user picks gea-shopfloor-cmm from the
main menu. Two options: Standard (default PC-DMIS + CLM + goCMM
+ Protect Viewer) or With DODA. Mirrors :keyence_submenu pattern.
- Write CMMVARIANT to W:\Enrollment\pc-subtype.txt so Install-FromManifest
on the bay can apply the PCTypes filter against gea-shopfloor-cmm-doda.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Imaging chain stalled on WJF00159 after FormTracePak Setup.exe forced
a reboot: SupportUser auto-logged in fine, briefly flashed something
(an HKLM\Run logon hook), then idle - no resume of Run-ShopfloorSetup.
Confirmed via diag-dispatcher.ps1: bay had AutoLogon working, RunOnce
empty, no Stage-Dispatcher.ps1 on disk, no setup-stage.txt.
Root cause: Run-ShopfloorSetup launches once from unattend XML's
FirstLogonCommands and has no self-resume mechanism. If anything cuts
it off mid-flight (FormTracePak Setup, eDNC MSI, Oracle install with
forced reboot, etc) the chain dies and nothing brings it back.
Stage-Dispatcher.ps1 in the repo is academic infrastructure that was
never wired into the live flow - startnet.cmd does not stage it and
nothing creates setup-stage.txt.
Fix: have Run-ShopfloorSetup register ITSELF as RunOnce at the top of
the script. The script is idempotent throughout (detection checks
skip already-done work) so re-entry post-reboot picks up cleanly.
Normal completion path removes the RunOnce so it does not re-fire
after the planned end-of-script reboot.
Also top up AutoLogonCount to 10 at script start. The unattend XML's
LogonCount=7 budget gets consumed across typical imaging reboots
(Office, Oracle, FormTracePak, Run-ShopfloorSetup explicit, sync-intune)
and an unplanned FormTracePak forced reboot pushes the counter past 0,
clearing AutoAdminLogon and parking the bay at the login screen.
Restoring the budget every Run-ShopfloorSetup entry keeps SupportUser
auto-logging in across any number of forced reboots until normal
completion. Lockdown's Autologon.exe sets its own AutoAdminLogon for
the ShopFloor user when it runs, so the post-completion natural
decrement to 0 does not affect the final state.
Verified via diag-dispatcher.ps1 capture on WJF00159 today - that bay
had AutoLogonCount=4 and no Stage-Dispatcher.ps1 on disk, which both
match this root cause + fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unattend XML sets LogonCount=7 for SupportUser autologon. Each interactive
login decrements the counter; at 0 Windows clears AutoAdminLogon and the
bay parks at the login screen with no one to fire RunOnce -> dispatcher
never re-runs -> imaging chain stalls.
Typical imaging burns several logons: Windows OOBE first logon,
post-Office reboot, Oracle install reboot, FormTracePak Setup.exe forced
reboot, Run-ShopfloorSetup's own shutdown /r at end of script, stage
advances. The unplanned FormTracePak reboot pushes the count past 0 on
some bays - the exact failure mode the WJF00159 imaging today hit, where
Stage-Dispatcher.ps1 had the new defensive RunOnce-re-register fix
landed but the dispatcher still never re-fired because there was no
auto-logon to trigger it.
Top up AutoLogonCount to 10 every time the dispatcher hits the
'shopfloor-setup' stage. Restores the autologon budget across any
vendor-forced reboots that fire during the stage. When sync-intune
finishes the whole pipeline, AutoLogonCount is left to decrement
naturally; by then lockdown's Autologon.exe has set its own
AutoAdminLogon for the ShopFloor user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User received three PowerShell scripts from a Mitutoyo source for
backing up + restoring FormTracePak settings per asset:
Export-FormtracepakInventory.ps1 - audit: enumerate files + reg keys
Backup-FormtracepakSettings.ps1 - capture: config + data + reg into
timestamped ZIP, manifest-driven
Install-FormtracepakSettings.ps1 - restore: replay ZIP to a new bay,
hash-skip identicals, backup
existing as .pre_restore_bak
Cleanup pass over the vendor-shipped versions:
- Strip Unicode box-drawing characters from banners (ASCII-only policy)
- Install: switch to [ordered]@{} for DefaultAppTargets/DefaultDataTargets
so fallback priority is deterministic
- Install: add -AssetNumber gate that defaults to per-asset SFLD path
\\tsgwp00525...\Shopfloor\backup\formtracepac\<AssetNumber>
- Install: timestamp the .pre_restore_bak filename so re-runs don't
clobber the previous backup
- Install: handle BackupPath being a directory containing
formtracepak_backup_*.zip files (picks newest)
- .bat launchers for each PS1 (bypass execution policy, double-click)
Not yet wired into 09-Setup-WaxAndTrace.ps1; pending reference-backup
capture from a known-good bay before promoting to imaging path. Today
the V6.213 vendor MSI install + per-asset cal ISO still handle the
imaging-time setup directly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
04-SetControllerNicIP.ps1 (imaging-time, runs once via Run-ShopfloorSetup):
- Finds the Realtek physical Ethernet adapter (controller NIC on every
collections bay; corp LAN is Intel)
- Skips any candidate with a DHCP default gateway (that one is the corp
LAN, not the controller)
- Skips any candidate already on 192.168.1.2
- Sets static 192.168.1.2/24, no gateway, clears DNS - matches the
manual procedure documented in post-deploy-debug-flowchart.md section 2B
- Refuses to guess when multiple Realtek NICs remain ambiguous
- Imaging-time only, not enforced via GE-Enforce so the tech can override
on a specific bay if needed without the drift-catcher reverting
Set-ControllerCredential.ps1 + manifest-entry-controller-credential.json:
- Break-glass cmdkey /add for the controller SMB share (\\192.168.1.1\md1
used by DNC). Scoped to the 12 Okuma LOC650 machine numbers (3201-3212).
- Manifest entry is detection-less so it runs every enforce cycle if the
script is armed (.ps1 extension); disarmed by default (.ps1.bak on the
share) so a coach can rename when a bay loses its credential without
the enforcer overwriting per-bay deviations between events.
- Smoke-tested end-to-end on win11 VM via QGA: SYSTEM context cmdkey /add
succeeds, cmdkey /list shows the entry. DNC service runs as LocalSystem
so SYSTEM vault is the right target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
If a bay is imaged with the 9999 placeholder (tech leaves the WinPE
prompt blank or types 9999), the lockdown+auto-login chain ends up at
the ShopFloor user with no real machine number. We had Check-MachineNumber.ps1
written - InputBox + Update-MachineNumber pulls per-machine NTLARS .reg
+ udc_settings_<N>.json from the SFLD share - but it only got registered
when a tech manually ran Configure-PC + toggled item 6. Fresh 9999 bays
never got the prompt, leaving the bay stuck on placeholder values until
someone noticed.
New Register-CheckMachineNumberTask.ps1 auto-registers the logon task
at imaging time. Gated on C:\Enrollment\machine-number.txt == 9999;
bays imaged with a real number never get the task (and any stale task
from a prior 9999-imaging on the same disk is cleaned up).
Wired into Run-ShopfloorSetup.ps1 right after the S: drive logon mapper
register. Skipped for self-contained types (display kiosks have no
machine number).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setup chains we do not control (FormTracePak Setup.exe, eDNC MSI, any
vendor installer that forces an immediate reboot) cut & $script off
mid-flight. Without this, the dispatcher never returns from the call
and the post-call Register-NextRun never fires, leaving the next boot
with no RunOnce + a stalled imaging chain. Observed today on WJF00159
where the FormTracePak v6.213 Setup.exe rebooted the bay before the
dispatcher could advance the stage.
Register the dispatcher's own RunOnce defensively before invoking the
sub-script. If a reboot interrupts the call, the next boot re-fires the
same dispatcher, which re-reads the still-'shopfloor-setup' stage file,
re-runs Run-ShopfloorSetup (every step is idempotent + detects already-
installed state), and converges. The existing post-call Register-NextRun
+ stage advance still run on the happy path - cheap, idempotent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
09-Setup-WaxAndTrace.ps1 Step 3:
- Detect Mitutoyo's burn-time typo on 218-378-13 series cal discs
(filenames carry a trailing space inside the probe ID component,
e.g. "Linear_X_218-378-13 _100072210.txt"). Their own .NET Setup.exe
calls FileSystemInfo.set_Attributes on the source path and throws
System.ArgumentException because the path contains an embedded space
component, crashing every cal apply on 218-378-13 bays (exit
-532462766 = 0xE0434352, .NET unhandled exception). Confirmed via
WER Event 1026 captured during today's WJF00159 imaging.
- When the buggy filenames are detected, bypass the broken vendor
Setup.exe and direct-copy data\*.* into
C:\Program Files (x86)\MitutoyoApp\Formtracepak\data\, renaming
each file to strip ' _' (space-underscore) -> '_'. Clear read-only
attr on each landed file. Older 218-458A discs have clean filenames
and still use the vendor Setup.exe path.
waxtrace-manifest.json:
- Drop DetectionValue=v14.15.26706 from both VC++ 2017 redist entries.
Windows Update routinely bumps the VS14 runtime to 14.16+ / 14.3x+,
the older Mitutoyo redist refuses to install over the newer (exit
1638 'Another version already installed') and the manifest engine
marked it as failed even though the runtime was fine. Detection is
now by registry-key+name presence, which any VC++ 2015-2022 redist
satisfies (they are backward-compatible).
startnet.cmd:prompt_waxtrace_asset:
- Replace free-text input with select-waxtrace-asset.ps1 arrow-key
picker driven from installers-post/waxtrace/calibrations/INDEX.csv.
- Map Y: enrollment share early so the picker can read INDEX.csv.
- Replace parens-in-parens block (echo of '(e.g. WJRP2335)' inside
the if-paren caused 'to was unexpected at this time' parse error
observed by tech mid-imaging) with goto-flow.
- Fall back to free-text prompt if picker unavailable or operator
presses Esc.
select-waxtrace-asset.ps1:
- Sort bays descending by asset tag so WJRP* lands at top of menu.
- Also staged as gea-shopfloor-waxtrace/select-waxtrace-asset.ps1 so
sync-waxtrace.sh ships it to installers-post/waxtrace/ on the share.
sync-waxtrace.sh:
- Push select-waxtrace-asset.ps1 next to INDEX.csv on the share.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the V6.0 captured-binary replay (pf-x86-MitutoyoApp.zip +
c-MitutoyoApp.zip + hklm-wow-mitutoyo.reg.gz) with a real vendor install
from FORMTRACEPAK-V6.213.iso mounted via Mount-DiskImage.
09-Setup-WaxAndTrace.ps1:
- Step 2 rewritten: Mount-DiskImage on
C:\WaxTrace-Install\formtracepak\FORMTRACEPAK-V6.213.iso, run the VB6
Setup.exe wrapper from the assigned drive letter (DRIVE_CDROM check
satisfied by virtual mount, no real CD needed), then Dismount.
- Header rewritten: drop captured/ description, note legacy fallback
remains in the repo (captured-binary/ unchanged) for manual recovery
if the v6.213 vendor install fails on a bay.
sync-waxtrace.sh:
- Push formtracepak/FORMTRACEPAK-V6.213.iso (2.0 GB) into the bundle
instead of captured/ payload. Override path via $FTPAK_ISO env var if
needed (e.g. testing a v6.213 patch ISO).
- Sanity check no longer demands pf-x86-MitutoyoApp.zip; only requires
prereqs/ + the manifest + dispatcher PS1.
playbook/utilities/convert-cal-iso.sh:
- New helper. Rebuilds a Linux-dd of a multi-session UDF cal disc into
a clean ISO9660+UDF hybrid that Windows Mount-DiskImage reads. mkisofs
reads the file via loop-udf, repacks single-session with the asset tag
as volume label. Run when `file CAL-*.iso` reports "data" (multi-session
UDF dd produced a Linux-readable but Windows-unreadable container).
Single-session 218-458A discs from dd are already ISO9660 and don't
need this.
Verified on win11 VM via qga: V6.213 ISO mounts, Setup.exe locatable.
14 cal ISOs all converted to ISO9660 (md5s refreshed in INDEX.csv),
re-synced to /srv/samba/enrollment/installers-post/waxtrace/calibrations/.
PXE share bundle now 2.0 GB total (V6.213 ISO + 14 cal ISOs + prereqs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under Intune-enforced LSA FIPS policy, Profilometer / VRAnalyzer /
VRInspection apps crash at device init when MD5CryptoServiceProvider's
ctor is called to verify the probe EEPROM calibration (see keyence3000.txt
+ .png in pxe-images for the dialog + stack).
Patch each .exe.config under C:\Program Files\KEYENCE\<model>\ with
<runtime><enforceFIPSPolicy enabled="false"/></runtime>. Scope is app-CLR
only; OS-wide Lsa FIPS policy stays enforced. CMMC posture: scoped
exception, non-CUI integrity hash, documented in SSP. Each affected bay's
hostname must be on InfoSec's FIPS-exception list before imaging.
09-Setup-Keyence.ps1 gates the patch behind model=vr3000 only. vr5000 /
vr6000 bays do not auto-apply. Verified on win11 VM via qga: 29 configs
across vr5000+vr6000 layouts (vr3000 install was incomplete on VM),
patched + idempotent on re-run, existing <runtime> children preserved.
Also verified on a real PC: 27 patched + 2 skipped (Keyence pre-shipped
the element in two configs), 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wax/Trace (gea-shopfloor-waxtrace):
- captured/ holds master FormTracePak v6.0 state (Program Files reg dump
gzipped, ARP entries) taken from a win11 VM where the CD-ROM-bound VB6
wrapper was driven to completion. xcopy + reg-import replays the install
on real bays without running the wrapper itself.
- 09-Setup-WaxAndTrace.ps1 rewrites the stub: installs prereqs via manifest
(VC++ 2008/2017 x86+x64, Sentinel HASP), expands the captured zips into
C:\Program Files (x86)\MitutoyoApp + C:\MitutoyoApp, imports the reg
hive, then mounts the bay's per-machine cal ISO (matched by asset tag
in machine-number.txt) and runs its Setup.exe.
- waxtrace-manifest.json lists the 5 prereqs with InstallShield-style
silent flags verified on the win11 VM.
- sync-waxtrace.sh ships captured-binary/ + prereqs + cal ISOs from
/home/camp/pxe-images/iso/mitutoyo-cal/ to
/srv/samba/enrollment/installers-post/waxtrace/ on the PXE box.
- select-waxtrace-asset.ps1 arrow-key bay picker for WinPE (parses
INDEX.csv from the cal share, offers "Other (new bay)" fallback).
- startnet.cmd: prompt_waxtrace_asset prompt, skip_waxtrace_stage xcopy
block (mirrors :skip_cmm_stage), machine-number.txt write covers bay
asset tag (WJRP*).
Keyence (gea-shopfloor-keyence) - now multi-model:
- vr3000/manifest.json + vr5000/manifest.json + vr6000/manifest.json
(current single-model VR-6000 moved into vr6000/ subdir). Each ships
the model's MSI silent-install + DetectionPath via ProductCode.
Big payloads (Data1.cab, Data11.cab) gitignored, staged via
sync-keyence.sh from /home/camp/pxe-images/iso/keyence/.
- 09-Setup-Keyence.ps1 dispatches by C:\Enrollment\keyence-model.txt
(written by startnet.cmd in :keyence_submenu) and points
InstallerRoot at C:\KeyenceInstall\<model>. DXSETUP probe widened
to all three Program Files paths (VR-3000 G2, VR-5000, VR-6000).
- startnet.cmd: :keyence_submenu picks vr3000/vr5000/vr6000,
:skip_keyence_stage xcopy block selectively stages chosen model bundle,
pc-subtype.txt also written = drops directly into existing GE-Enforce
PCSubType wiring (looks for gea-shopfloor-keyence-<model>\manifest.json
on the tsgwp00525 share for ongoing enforcement, no dispatcher change
needed).
- sync-keyence.sh mirrors sync-waxtrace.sh pattern.
Verified silent MSI install for VR-3000 G2 v2.5.0 and VR-5000 v3.3.1 on
the win11 VM 2026-05-18 with /qn /norestart ALLUSERS=1 REBOOT=ReallySuppress
TRANSFORMS=1033.mst. boot.wim on 172.16.9.1 wimupdate'd with the new
startnet.cmd.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
check-bios.cmd: drop literal `^` from BIOS_STATUS (caret survives quoted
SET so substring search for `->` never matched). Write X:\bios-fired.flag
on flash_done + staged paths so startnet.cmd can detect via if-exist.
startnet.cmd: replace `call set` substring-replace with `if exist
X:\bios-fired.flag`. Move push to after W:\Enrollment xcopy completes
(before Y: cleanup) so dashboard reflects "BIOS firmware update" stage
once file staging is done, matching user mental model of imaging order.
Tested flag-file logic in win11 VM cmd.exe: missing -> SKIPS, present
-> FIRES, removed -> SKIPS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for the AESFMA swap path:
1. Removed the X509Chain root-thumbprint pre-check. Bay user reported
"claims connect not yet operational, but i was able to manually
connect" - meaning the cert IS in LocalMachine\My but
$chain.Build() returns a partial chain (probably missing an
intermediate in the local trust store), so our root-thumbprint
match returned false and Monitor never even tried the netsh
connect. Letting netsh attempt directly - it's the source of
truth on whether EAP-TLS auth succeeds. Rate-limited to 30s
between attempts to avoid log spam when AESFMA truly isn't
reachable.
2. Bumped post-connect verify sleep 8s -> 15s. WLAN auth + DHCP can
take longer than 8s on first attempt.
3. New: once Test-AESFMAConnected returns true and INTERNETACCESS
is deleted, force-run GE_ReportIP_3_v1.EXE /ForceUpdate=True /S
so the webhook gets the corp-AESFMA IP immediately instead of
waiting for the next DHCP-change trigger (which may never fire
if AESFMA was the bay's first 10.x lease). $script:cache.
ReportIpForced caches the one-shot fire.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Monitor's Get-Snapshot already tracks Phase 1-5 (Intune Registration,
Device Configuration, Software Deployment, Credentials, Lockdown).
The webapp dashboard only saw a single idx=7 push for the entire
post-PPKG / pre-lockdown window, so the friendly label couldn't
reflect "where is this bay actually". Operator looking at the
dashboard had no idea whether to assign category or hit ARTS for
lockdown next.
Monitor now pushes additional idx=7 entries as it crosses Phase
boundaries:
- On DeviceId capture: "Intune Device ID captured" (existing)
- On Phase 2 done (SFLD policy delivered = category was assigned):
"Phase 2 SFLD policy delivered (device configuration)"
- On Phase 1-4 all complete: "Phases 1-4 complete - ready for
lockdown (ARTS request)"
- On lockdown done: idx=8 (existing)
imaging.html maps the stage_string substring to friendly labels:
- default idx=7 -> "Registered - assign category"
- 'sfld policy' / 'phase 2' -> "Phase 2 - device configuration"
- 'credentials' / 'phase 4' -> "Phase 3 / 4 - DSC + credentials"
- 'ready for lockdown' / 'request lockdown' -> "Ready - request
lockdown" (hint: click ARTS request)
Operator now knows exactly when to act vs when to wait.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs causing "AESFMA cert detected, connecting AESFMA..." to log
over and over even after AESFMA is already up:
1. Regex 'SSID\s*:\s*AESFMA.*?State\s*:\s*connected' required SSID
line BEFORE State line. Actual netsh wlan show interfaces order
on Win11 is "Name / State / SSID" - State comes FIRST. The non-
greedy match never succeeded. Always thought AESFMA wasn't
connected. Refactor to a Test-AESFMAConnected helper that splits
output into per-adapter blocks and checks SSID + State independently,
tolerating either order.
2. Added a fast-path at top of the WiFi-swap block: if AESFMA is
already connected (no help needed from us), just delete
INTERNETACCESS if still present and flip the cache flag to stop
running this block. Previously the block only set the flag after a
successful connect-then-verify-then-delete cycle; if AESFMA was
already up at first check, the cycle "succeeded" each tick but
the flag never flipped, producing the log spam.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Phase 1 done gate was requiring 'AESFMA WLAN connected' in addition
to the data-side signals (AAD + Intune + EmTask + baseline). If the
bay never reached AESFMA (cert never landed, RADIUS unreachable),
Phase 1 stayed IN PROGRESS forever even though Intune registration
was actually complete. Reverting to the data-side-only definition.
2. New webapp endpoint POST /imaging/<serial>/laps stores a LAPS
password in the session JSON so it survives the 5s dashboard
auto-refresh. Empty body clears the field. Daily reset of the
server (cron/restart) is the lifetime cap on stored passwords.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Smoking gun for "Monitor's on-screen QR works but no idx=7 push lands
on the PXE dashboard". Win11's dsregcmd emits ANSI VT100 escape codes
(e.g. \x1B[7mDeviceId\x1B[0m :) around field labels. Captured output
strings then have those codes between "DeviceId" and ":". The strict
regex 'DeviceId\s*:\s*<guid>' fails because \s* doesn't match ANSI
escape chars. $script:cache.DeviceId stays null, idx=7 push never
fires.
Build-QRCodeText was unaffected because it uses Select-String 'DeviceId'
(substring match, tolerates anything in between) then splits on ':'.
Fix: strip ANSI sequences via -replace '\x1B\[[0-9;]*[A-Za-z]', '' before
running the regex. Same pattern covers all CSI sequences dsregcmd uses.
Also force Out-String to get a single string back (was an array of lines
from 2>&1; -match on arrays returns matching elements but $matches
behavior across mixed objects is fragile).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Empirical: a fresh-imaged bay often hasn't finished AAD-join + first
Intune sync by 60s, so the post-PPKG-reboot Monitor instance starts
without DeviceId visible to dsregcmd yet. Doubling the settle to 120s
gives MDM more time to land baseline policies before the reboot,
which means the post-reboot Monitor sees AAD-joined + DeviceId on
first tick and fires idx=7 immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenText / Host Explorer shortcut filenames vary by installed profile
(e.g. 'WJ Shopfloor OpenText.lnk', 'WJ Shopfloor.lnk', 'HostExplorer
ShopFloor.lnk'). The taskbar-pin path in site-config.json hardcodes
'Shopfloor Tools\WJ Shopfloor.lnk' - mismatches the actual filename
so 07-TaskbarLayout silently skips pinning it.
Drop OpenText/ShopFloor/HostExplorer pattern moves from 06's
categorization regex. Shortcuts stay at the public-desktop top
level where the OpenText installer placed them. Tech sees the
icon on the desktop, no taskbar pin (the variable filename made
the pin unreliable anyway).
Other categories (UDC, eDNC, NTLARS, etc with stable filenames)
still move into Shopfloor Tools and pin correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Walk Cert:\LocalMachine\My, build each cert's chain, look for chain
element with thumbprint 27F0C9A22B28CE7687B115A29E31BF4B3ABB180F.
That's the AESFMA.xml TrustedRootCA value = the GE Aerospace
FreeRADIUS root that AESFMA EAP-TLS validates against. A client cert
chained to that root is the SCEP-provisioned AESFMA machine cert.
Combined with the verify-before-delete connect attempt, this gives
two gates:
1. Cert deterministically exists + chains correctly
2. netsh wlan connect to AESFMA actually reports State=connected
Only after both pass does INTERNETACCESS get deleted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Old gate (SCEP cert in LocalMachine\My with Client Auth EKU) was both
too loose (matches non-AESFMA certs) and unable to verify the cert
chains to GE's RADIUS root. INTERNETACCESS got deleted before AESFMA
could actually authenticate, orphaning the bay.
New flow: when Phase 1 essentials (AAD + Intune + EmTask + baseline)
are complete, ATTEMPT netsh wlan connect AESFMA with INTERNETACCESS
still up as fallback. Wait 8s, parse netsh wlan show interfaces for
SSID=AESFMA + State=connected. Only delete INTERNETACCESS after
operational verification. If AESFMA connect fails (cert not provisioned
yet, RADIUS server unreachable, etc), keep INTERNETACCESS and retry
next tick. Loop runs every 5s while DeviceIdReported is false, so the
swap fires as soon as AESFMA is operationally viable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeviceId may not be in dsregcmd output the moment Monitor starts after
PPKG reboot - takes a few minutes for AAD-join to settle. Default 30s
PollSecs leaves wide gaps where Monitor isn't checking. Sleep 5s
instead while DeviceIdReported is still false. Once captured + idx=7
push lands, falls back to PollSecs (30s) for the rest of the loop.
Worst case for QR-on-dashboard latency: ~5 seconds after dsregcmd
starts returning a DeviceId.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User constraint: GE-issued LAPS-prompt reboot lands ~1 minute after
Report IP posts its log. Need the QR on the PXE dashboard BEFORE
that reboot or the operator has no way to look up the device for
LAPS retrieval.
Previously idx=7 was gated on Phase 1 essentials (AAD + Intune
enrolled + EmTask + baseline policies >=5). Those flips happen
later than DeviceId capture (dsregcmd shows DeviceId the instant
AAD-join completes during PPKG). Dropping the gate so idx=7
fires the moment the cache has a DeviceId. Phase 1 row on the
on-bay Monitor display still has its own AESFMA-required gate
for operational completeness; only the dashboard push is moved
earlier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related fixes for the WiFi handoff timing:
1. WiFi swap (delete INTERNETACCESS + connect AESFMA) was firing on
Phase 1 essentials being green (AAD + Intune + EmTask + baseline
policies >=5). That signal flips ~minutes BEFORE the Intune SCEP
machine cert actually lands in LocalMachine\My. Without the cert,
AESFMA EAP-TLS auth fails and the bay has no path at all (we just
deleted INTERNETACCESS). Stuck.
New gate: walk Cert:\LocalMachine\My for any cert with Client
Authentication EKU (1.3.6.1.5.5.7.3.2). When that's present, SCEP
has delivered, AESFMA EAP-TLS will succeed. Swap then fires safely.
2. Phase 1 row on the on-bay Monitor display now ALSO requires
AESFMA to be actively connected (parsed from netsh wlan show
interfaces: SSID=AESFMA + State=connected). Phase 1 stays IN
PROGRESS until the bay is operationally on corp WLAN, not just
data-side enrolled. Matches user request "not complete phase 1
until AESFMA is ready".
idx=7 dashboard push still fires on the original Phase 1 essentials
gate so the QR appears as soon as Intune registers the device,
independent of AESFMA join timing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GE Report IP filters Get-NetIPAddress on StartsWith("10.") and PXE LAN
addresses are now 172.16.9.x which the filter skips naturally. The
disable-then-re-enable workaround was only needed when PXE LAN was
10.9.100.x and bays leaked that IP to the GE webhook. With the renumber
that whole flow is dead weight.
Removed:
- playbook/shopfloor-setup/Shopfloor/lib/Disable-WiredNics.ps1 (file)
- Run-ShopfloorSetup: Disable-WiredNics call after PPKG returns
- Run-ShopfloorSetup: "GE Re-enable Wired NICs" SYSTEM task registration
- Monitor-IntuneProgress: reportIpLog-gated wired re-enable + idx=7 retry
- Monitor-IntuneProgress: reportIpDone gate on Phase 1 done check
Side benefit: stages 2-6 dashboard pushes no longer go dark mid-flow
(used to die between idx=6 and idx=7 when wired was off). Phase 1 row
on the Monitor screen now flips COMPLETE on the natural AAD + Intune
+ EmTask + baseline-policies condition instead of waiting on the
Report IP log file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single-site bay-stuck issue at WJ: GE Intune Report IP script filters
Get-NetIPAddress on StartsWith("10.") and posts everything matching
to the GE Tines webhook. Bays at WJ get the PXE LAN 10.9.100.x IP
captured and reported -> GE backend tags bays as on a non-corp 10.x
subnet -> dynamic group eligibility for SFLD policy never matches.
Other GE sites work because their PXE LANs aren't on 10.x at all.
Renumber PXE LAN to RFC1918 172.16.9.0/24 so the GE filter naturally
skips wired PXE addresses without any disable-NIC dance.
Server-side already in flight (netplan dual-bound, dnsmasq scope +
boot URL repointed, blancco preferences + grub.cfg + iPXE GetPxeScript
all sed'd to 172.16.9.1). This commit is the playbook / scripts /
docs side: 109 hits across 35 files sed'd in one shot.
After this lands + boot.wim is rebuilt + bays renumber off DHCP,
the 10.9.100.1 binding will be dropped from netplan as the final
cleanup step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pair with the INTERNETACCESS -> AESFMA WiFi-swap commit. Once
AAD-joined + IntuneEnrolled + EmTaskExists + baseline policies
all true AND DeviceId is captured, push idx=7 to PXE dashboard
with the DeviceId immediately - don't wait for the Report IP log
(which depends on AESFMA join + script timing).
Side note: the legacy wired-NIC re-enable + reportIpLog-gated
idx=7 push block earlier in Get-Phase1 still exists. Both paths
guard on $script:cache.DeviceIdReported so only one fires, but
that block is dead-ish under the new WiFi-swap flow (no wired
disable -> no NIC state file -> re-enable block no-ops; Report
IP log gate may still fire idx=7 if Phase 1 essentials haven't
all flipped yet but Report IP did). Worth cleaning up next pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When Intune registration lands (AAD-joined + IntuneEnrolled + EnterpriseMgmt
task present + baseline policies >=5), the bay is presumed to have its
SCEP-provisioned machine cert in LocalMachine\My. At that point the
INTERNETACCESS profile (172.16.x guest/internet WiFi) is no longer
useful - it just keeps the bay on a non-corp range so Report IP can't
find a 10.x to POST and the SFLD assignment filter never matches.
Action: in Get-Phase1, once all four registration signals are green,
fire 'netsh wlan delete profile name=INTERNETACCESS' then immediately
'netsh wlan connect name=AESFMA ssid=AESFMA'. Bay drops onto corp WLAN
with EAP-TLS, picks up a 10.x lease, Report IP fires cleanly. One-shot
per Monitor lifetime via $script:cache.InternetAccessDeleted flag.
This is the alternative to pre-staging the AESFMA profile during
imaging (which was reverted). AESFMA profile is assumed to exist
already because Intune's WiFi config profile delivers it during the
same enrollment that just completed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User call: don't install AESFMA profiles during imaging preinstall.
Removed the MA package copy + MA4NetworkConfigv2.bat invocation
from Run-ShopfloorSetup line 43 area. .gitignore additions for
profile XML patterns are kept - those are harmless safety net.
PXE share's /srv/samba/enrollment/MachineAuth/ staging directory
is left in place (not deleted) - no consumer references it after
this revert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GE's Intune-deployed ReportIPAddresses_v2.ps1 filters Get-NetIPAddress
with $_.StartsWith("10.") - too broad for WJ where PXE LAN is 10.9.x.
Can't modify the GE script (signed). Workaround: run our own POST to
the same Tines webhook with a tight subnet filter, beating GE's
script to the punch.
New Invoke-FilteredReportIP.ps1 (lib/):
- Walks Get-NetIPAddress -AddressFamily IPv4
- Filters strictly to 10.134.48.0/23 OR 10.48.249.0/26 (WJ corp)
- POSTs to https://tines.apps.geaerospace.com/webhook/.../... with
{host, fqdn, IP, force_update} body matching GE's payload shape
- Local dedup via C:\ProgramData\GEA\FilteredReportIP\last-ip.txt
- 6 retries with 10s backoff on transient HTTP error
- Logs to C:\Logs\FilteredReportIP.log
Monitor-IntuneProgress main loop calls it each tick until it
succeeds once. After success, $filteredReportIpSucceeded flag short-
circuits further attempts.
If WJ later moves to a different VLAN, edit the $allowedRanges array
in Invoke-FilteredReportIP.ps1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hypothesis test for WJ Phase 2 stuck issue. GE Report IP script
filters Get-NetIPAddress on StartsWith("10.") - WJ bays don't see
ANY 10.x because:
- PXE LAN is 10.9.100.x (we'd disable wired anyway to avoid leak)
- Internet WiFi at site is 172.16.x (filter rejects)
- AESFMA corp WiFi (10.x) requires machine cert that Intune SCEP
provisions a few minutes AFTER PPKG enrollment
Result: Report IP webhook gets nothing -> GE backend never sees the
bay -> bay never enters the dynamic group that SFLD policy is
assigned to. Other GE sites work because their corp WiFi/wired is
on a real 10.x corp network and the script always finds a 10.x to
report.
Drop the MA package (8021x.xml + AESFMA.xml + multi-NIC bat) onto
each bay early in Run-ShopfloorSetup, run MA4NetworkConfigv2.bat to
import both profiles to every physical wired + wireless adapter.
AESFMA.xml patched to connectionMode=auto (default V02 was manual)
so WLAN service auto-joins as soon as the SCEP cert lands. Bay
gets a real 10.x corp address. Report IP webhook fires cleanly.
Profile XMLs (8021x.xml, AESFMA.xml, BLUESSO.xml, WiFi-Profile.xml,
*.wlanprofile, *.lanprofile) added to .gitignore - they contain
GE-internal SSID + trusted-root thumbprint and are staged on the
PXE enrollment share at /srv/samba/enrollment/MachineAuth/ instead
of git.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Field test: bay imaged end-to-end, Monitor saw Report IP log,
captured DeviceId (Phase 1 went COMPLETE on screen + QR rendered
from dsregcmd), but the idx=7 push to the PXE dashboard never
landed before the Intune-triggered LAPS-prompt reboot. Root cause:
Enable-NetAdapter + 1s sleep doesn't give Windows time to renew
DHCP + populate routes before Send-PxeStatus POSTs to PXE webapp.
Push silently caught (Send-PxeStatus has try/catch), next tick was
30s away, LAPS reboot fired in between.
Two changes:
1. Sleep bumped 1s -> 5s after Enable-NetAdapter so wired path is
actually carrying traffic before we POST.
2. When the tick that did the re-enable is also the push tick,
retry Send-PxeStatus up to 6 times with 2s spacing (~12s total)
instead of one-shot-then-give-up. Surfaces the warning to the
transcript if all attempts fail so we can diagnose next time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Field bay surfaced two bugs in one diag dump (mdm-diag-F907T5X3 -
6PPSF24):
1. GE Proactive Remediation Report IP actually writes
GE_Report_IP_Address_2_5.LOG (uppercase .LOG), not the .txt I
assumed. Globs in two places had .txt filter -> never matched ->
Phase 1 stuck IN PROGRESS forever even after the file landed and
wired-NIC re-enable never fired. Drop extension from both globs
in Monitor-IntuneProgress.ps1 (id=7 push gate + p1Done check).
2. The "GE Re-enable Wired NICs" SYSTEM task registered by
Run-ShopfloorSetup was polling Autologon_Remediation.log for
"Autologon set for ShopFloor" - a lockdown-time signal. Re-enable
needs to fire at Report-IP time (well before lockdown) so that
Monitor can push idx=7 with the QR before the Intune-triggered
LAPS-prompt reboot. Repoint the SYSTEM task's poll to
C:\Logs\GE_Report_IP_Address* (any extension).
Plus minor UX: copy button next to the Intune device ID on
/imaging dashboard so techs can grab the GUID without having to
double-click-select the <code>.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>