Commit Graph

265 Commits

Author SHA1 Message Date
cproudlock
9122b28c31 webapp: imaging progress dashboard + serial column on reports list
Adds end-to-end progress tracking for PXE imaging sessions and surfaces
each Blancco report's BIOS serial in the report list.

webapp:
  * services/imaging_status.py - JSON-per-serial state store under
    IMAGING_DIR (default /var/log/pxe-imaging). Atomic write via
    tempfile + rename. log_tail capped at 50 lines. Merges partial
    updates so clients can post just the current_stage tick.
  * config.py - new IMAGING_DIR env-overridable path.
  * services/csrf.py - explicit exempt list for machine-to-machine
    endpoints; /imaging/status is the first entry. Air-gapped LAN;
    trust-by-network for client posts.
  * app.py - four new routes:
      GET  /imaging               dashboard (renders all sessions)
      POST /imaging/status        client status push (JSON body)
      GET  /imaging/<serial>.json raw session JSON for ad-hoc polling
      POST /imaging/delete/<s>    clear a session from the dashboard
    Also parses each Blancco XML in the /reports list to surface
    system.serial + system.model columns.
  * templates/imaging.html - Bootstrap dashboard with per-session
    cards (state badge, progress bar, stage idx/total, mac, elapsed,
    log tail). meta http-equiv refresh=5 for auto-tick.
  * templates/base.html - new "Imaging Progress" nav entry.
  * templates/reports.html - Serial + Model columns added.

playbook:
  * shopfloor-setup/Shopfloor/lib/Send-PxeStatus.ps1 - new helper.
    Dot-source this then call Send-PxeStatus -Stage X -StageIndex N
    -StageTotal M from any stage script. BIOS serial via CIM, MAC via
    Get-NetAdapter, pctype + machinenumber from C:\Enrollment.
    Failures are swallowed to a local log so a network blip doesn't
    block imaging.
  * shopfloor-setup/Run-ShopfloorSetup.ps1 - dot-sources helper +
    posts at three coarse milestones (start, PPKG enrollment,
    handoff to Monitor-IntuneProgress).
  * shopfloor-setup/gea-shopfloor-keyence/09-Setup-Keyence.ps1 -
    posts at session start + after Install-FromManifest with
    succeeded/failed status derived from $rc. Other 09-Setup-*.ps1
    scripts can follow the same pattern.

ID is BIOS serial (stable across WinPE -> Windows transition and
across reboots, unlike hostname which is random pre-PPKG). Operator
already knows the serial of the bay they imaged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:07:18 -04:00
cproudlock
1d3f21f814 keyence: document Data1.cab staging requirement, gitignore the 560 MB cab
VR-6000 Series Software.msi is an InstallShield MSI that references
Data1.cab in the same directory for its compressed payload. The cab was
never staged into the repo's keyence installers/ dir, so msiexec exited
1603 with "SECREPAIR: Failed to open the file ... Data1.cab" on every
imaging run (see Logs/Keyence/install.log on a failed bay for the
canonical signature). Only the 1.75 MB MSI was committed; the 560 MB
cab lives on the GE-Enforce SFLD share at
tsgwp00525\sfld$\v2\shared\dt\shopfloor\gea-shopfloor-keyence\apps\.

This commit doesn't add the cab itself (560 MB; same gitignore convention
as PrinterInstallerMap.exe and other large binaries). Instead it pins the
staging requirement in two places:

  * .gitignore: explicit entry with the SFLD share path so a future
    operator wiring up a fresh PXE server build knows where to source it.
  * keyence-manifest.json _comment: documents the dependency next to the
    MSI declaration that needs it.

The local repo at /home/camp/projects/pxe now has the cab staged in
playbook/shopfloor-setup/gea-shopfloor-keyence/installers/ for the next
USB build. Rebuilding the Keyence image and re-imaging the failed bay
should now reach DisplayVersion 4.3.7 detection successfully.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:36:52 -04:00
cproudlock
974accf98a blancco: fix silent prefs fallback, suspend trap, display blank + add View
End-to-end fixes for Blancco Drive Eraser PXE flow uncovered by chasing
"reports never reach SMB share" across two air-gapped sites:

playbook/blancco-init.sh:
  * Drop silent || true on wget of preferences.xml + config.xml. Fail
    loud with shell-drop if download or marker grep fails. Background:
    airootfs /opt/scripts/validate_preferences.sh restores
    /albus/preferences.save (factory defaults, empty network_share) if
    xmllint fails. wget failure made every report silently land nowhere.
  * Clobber /albus/preferences.save with the same served file so even if
    the validator fallback fires, the SMB target survives.
  * Bind-mount /dev/null over /sys/power/{state,disk,mem_sleep,autosleep}
    before switch_root. Albus's license-retry path writes /sys/power/state
    directly (bypassing systemd targets); this is the last-line block.
  * /dev/null symlinks for sleep/suspend/hibernate systemd targets in the
    airootfs overlay + logind drop-in with IdleAction/Handle*=ignore.
    Three independent layers because cmdline systemd.mask alone is bypassed
    by direct /sys/power/state writes.
  * xinitrc.d/00-no-screen-blank.sh runs xset s off -dpms + setterm
    -blank 0 -powerdown 0 so the Blancco GUI doesn't blank during long
    erasures.
  * Removed the 20-failsafeDriver.conf "modesetting" pin. modesetting
    needs DRM/KMS which we disable on kernel cmdline; "vesa" also failed
    on NVIDIA. With the pin gone Xorg auto-picks fbdev which uses the
    kernel framebuffer from vga=normal - works across Intel, AMD, and
    older NVIDIA without nouveau.

playbook/pxe_server_setup.yml:
  * dnsmasq.conf: explicit empty-value dhcp-option=3 + dhcp-option=6.
    Without them, dnsmasq defaults to sending its own IP as router AND
    DNS. Commenting the configured-value lines did NOT disable the push
    (root cause of "wired keeps picking up 10.9.100.1 as gateway").
  * Split the Blancco config.img extraction and preferences.xml deploy
    into separate tasks. The previous shell-with-creates: gate caused
    playbook re-runs to skip the prefs deploy entirely after first run.
  * Added a validation task that runs python3 xml parse + grep on the
    deployed preferences.xml to fail the playbook at deploy time if the
    SMB markers are missing.
  * Added Environment=TZ=America/New_York to the pxe-webapp systemd
    service so report mtimes and audit log render in Eastern time even
    if the Python process is started before timedatectl converges.

webapp:
  * services/blancco_report.py: parse Blancco's XML report format
    (recursive <entries name="..."> walker) into a friendly dict.
  * templates/report_view.html: Bootstrap "Drive Erasure Certificate"
    layout - hero summary, customer + system cards, per-drive cards with
    step-by-step erasure timeline, document signing footer with
    integrity hash detail.
  * /reports/view/<filename> route + View button on the reports list
    (XML reports only; PDFs still download).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:38:54 -04:00
cproudlock
adc8d50e66 pxe: arch-aware NBP + undionly.kpxe for legacy BIOS clients
Legacy-BIOS PXE clients booting Blancco reported "NBP is too big to
fit in free base memory". Cause: dnsmasq unconditionally served
ipxe.efi (~675KB EFI binary) which legacy BIOS PXE ROMs cannot
execute and which exceeds their NBP cap.

Fix:
- Add undionly.kpxe (~70KB BIOS-mode iPXE, from boot.ipxe.org).
- dnsmasq: dhcp-match on option:client-arch,0 (BIOS) -> undionly.kpxe;
  default (everything else, including UEFI x86_64 arch 7 and 9) keeps
  getting ipxe.efi. Tag form is reversible: if the match fails to
  evaluate, fallback is the working EFI path, not the new binary.
- Ansible TFTP-copy loop: mirror undionly.kpxe alongside ipxe.efi.
- .gitignore exception: track the open-source kpxe binary so the
  air-gapped USB build stays self-contained.

UEFI clients unchanged. Blancco/Clonezilla/WinPE chain after the
iPXE menu is identical regardless of which iPXE variant delivered it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:13:44 -04:00
cproudlock
3896667c90 Set-MachineNumber: handle duplicate-PC reassignment (real -> real)
Tech catches a PC imaged with a wrong machine number. Previously the
share restore (NTLARS .reg + UDC settings + UDC live data) only fired
on the placeholder->real transition, so a real->real change rewrote
only UDC JSON, eDNC reg, and MTConnect Devices.xml - leaving the wrong
NTLARS config in place.

Update-MachineNumber.ps1: replace the placeholder-only guard with an
any-change guard so the share restore block fires on reassign too.
The existing one-shot migrated/ consumption keeps live-data restore
idempotent. Also writes C:\Enrollment\machine-number.txt to keep
imaging-time scripts in sync.

Set-MachineNumber.ps1 (both collections + nocollections): show a
confirmation dialog when reassigning between two real numbers, naming
old/new and listing what gets pulled. Audit each call to
C:\Logs\Shopfloor\reassign.log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:13:30 -04:00
cproudlock
4d6438285b playbook: document USB-C 5 Gbps NIC bridge override on netplan task
The "Configure static IP for PXE interface" task writes a flat
single-NIC netplan config. Live PXE server (10.9.100.1) overrides
this with a bridge config bonding the USB-C 5 Gbps NIC and the
onboard NIC into br-pxe, because the onboard NIC alone cannot
sustain the imaging throughput required by the floor.

Add a comment block above the task warning that re-running it on
a box already using the bridge config will replace the bridge with
a flat single-iface config and likely break the PXE LAN. The full
bridge YAML is included for reference. Recovery is via
/etc/netplan/50-cloud-init.yaml.pre-gold-swap (preserved by netplan
backup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:23:37 -04:00
cproudlock
99802ebbc9 BIOS: add OptiPlex 7080 (1.37.0)
Models.txt entry maps "7080" substring (matches WMI csproduct name
"OptiPlex 7080") to OptiPlex_7080_1.37.0.exe. BIOS .exe already
deployed to /srv/samba/winpeapps/_shared/BIOS/ on the live PXE
server via download-drivers.py.

Also adds docs/geastandardpbr-overrides.md tracking the local
geastandardpbr/ edits (user_selections.json + HardwareDriver.json
get a 7080 entry under "D11 OptiPlex Family") that the gitignore
prevents from being tracked directly. Includes a Python snippet
to idempotently re-apply after a fresh USB import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:23:28 -04:00
cproudlock
3a5c907cbf Run-ShopfloorSetup: skip GE-Enforce + S: map on Display PCs
Display kiosk user cannot authenticate to the tsgwp00525 SFLD share,
so any share-dependent enforcement task on Displays would fail every
cycle. Display is now self-contained: kiosk EXE installs at imaging
time via preinstall.json (Install-KioskApp.cmd) and Edge kiosk
policies via 09-Setup-Display.ps1. No ongoing SFLD-share dependency.

Gate both registrations behind a $noEnforceTypes alias group so
either pcType form (Display, gea-shopfloor-display) hits the skip
path. Other PC types still register both tasks unchanged.

Verified on win11 VM: matrix test confirmed Display + gea-shopfloor-
display SKIP both gates while Standard / CMM / gea-shopfloor-
collections still REGISTER.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:01:34 -04:00
cproudlock
39f9945382 Run-ShopfloorSetup: PPKG handoff + pre-PPKG network gate
Block run-enrollment when this PC has no WiFi adapter and no default
route. PXE imaging LAN has no DHCP gateway, so towers without WiFi
get stuck in PPKG enrollment (AAD + Intune endpoints unreachable)
and require a re-image. Recurring failure mode observed 2026-05-05.
Tech-facing R/X retry+abort prompt walks them through plugging into
a corp wall jack.

Replace plain post-PPKG reboot with handoff to Monitor-IntuneProgress
-PostPpkg: cancel the pending shutdown timer, run a 180s settle so
MDM can push the baseline policy, render live status during settle,
then issue a clean reboot. The persistent @logon sync_intune task
resumes tracking on the next boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:00:49 -04:00
cproudlock
0e105fdbf2 Run-ShopfloorSetup: vendor-agnostic wired NIC re-enable filter
Filter by PhysicalMediaType + HardwareInterface instead of
InterfaceDescription regex. Name/description varies per vendor
(Realtek Gaming GbE, Intel I219-V, etc.) so a name-only filter
missed adapters on some hardware. Keep an InterfaceDescription
negative guard for drivers that mis-report PhysicalMediaType.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:00:01 -04:00
cproudlock
72e4058d89 GE-Enforce: bump enforcerVersion '2.5' -> '2.5.1' so convergence-check shows the remount fix landed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:08:27 -04:00
cproudlock
c8ef05b869 GE-Enforce: re-mount W: before status write + null guards
Root cause of fleet not reporting: long-running entries (UDC's
WaitTimeoutSec=120) keep the dispatcher CPU-busy with no SMB traffic
to W:. SMB server times out the idle session. W: stays as a "mapped"
drive letter on the client but Path operations against it fail with
weird errors (e.g. "Cannot bind argument to parameter 'Path' because
it is null" via downstream Join-Path / Test-Path null cascades).

Fix:
- Re-attach W: at the top of the status write-back block (cheap; if
  still alive net use returns 'already mapped'; if dead, freshly
  remounts).
- Null-guard $hostname (fall back to 'UNKNOWN') and explicitly throw
  if $driveLetter is unset (catch surfaces a clear error in log).
- Pair with UDC manifest WaitTimeoutSec reduction 120 -> 60 on the v2
  share to limit how long the SMB stays idle in the first place.

Surfaced via blah*.txt log captures from 3 deployed bays (3105, 3115,
3116, 3118 era) - all showed "Status write-back failed: Cannot bind
argument to parameter 'Path' because it is null" while everything
upstream (manifest entries) ran clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:02:16 -04:00
cproudlock
707a0f94c2 GE-Enforce: prefer DNC reg MachineNo over machine-number.txt
machine-number.txt holds the imaging-time MN. PCs imaged with
placeholder 9999 (tech intends to flip via Set-MachineNumber later)
keep 9999 in that file even after Update-MachineNumber writes the
real MN to HKLM:\...\Dnc\General\MachineNo. Status.json was reporting
9999 across the fleet because of this.

Now reads DNC reg first; only falls back to machine-number.txt if reg
is missing or also 9999. Existing convergence-check.txt unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:12:55 -04:00
cproudlock
9b2ee11840 GE-Enforce: write machineNumber + bump enforcerVersion to 2.5
Adds machineNumber field to status.json (read from C:\Enrollment\machine-number.txt).
Bumps enforcerVersion to '2.5' so check-fleet-convergence.txt's column
flips when fleet picks up the self-update. Pairs with the Install-FromManifest
v2.5 (WaitTimeoutSec) so both bumps land together.

Convergence-check txt updated to display the new MN column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:55:29 -04:00
cproudlock
feae2d6929 Install-FromManifest: fix Test-Installed -> Test-AppInstalled typo
Bug in lib v2.5 introduced 2026-05-04: WaitTimeoutSec branch called the
non-existent function Test-Installed. Should be Test-AppInstalled (the
actual function name elsewhere in the same file).

Caught when J test ran and surfaced "The term 'Test-Installed' is not
recognized" after the WaitTimeoutSec=120 fired on UDC. UDC entry then
exited -1 (Process.Start failed catch block) instead of 0 (success
post-detection-recheck). Functional impact was containable - dispatcher
moved on to next entries - but UDC always reported FAILED.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:37:04 -04:00
cproudlock
802d85e685 ntlars-backups: revert FMSHostPrimary to WJFMS3.AE.GE.COM (16 chars)
Per user 2026-05-04: the 31-char WJFMS3.APPS.WLM.GEAEROSPACE.NET
empirically did NOT work end-to-end, contradicting the earlier "just
tested it works" report (which turned out to confirm a different
codepath, not eDNC FMS prescan).

Re-aligns with the original Ghidra-derived 20-byte buffer constraint:
WJFMS3.AE.GE.COM = 16 chars + null = 17 bytes, fits cleanly in
CPreScan + CDoPersonnel's local_28[20] / size=0x14 RegQueryValueExA
buffer. Anything > 19 chars hits ERROR_MORE_DATA and falls into the
"Cannot Read field" failure path.

FMSHostSecondary unchanged at 10.233.112.158 (IP literal). v2 mirror
also updated: gea-shopfloor-{collections,nocollections} drift-catcher
RegValue, common/Set-FmsHostsEntry.ps1 script, common/manifest.json
hosts pin entry name. Re-run SYNC-TO-PROD.md robocopy to push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:28:18 -04:00
cproudlock
8baae43e08 Install-FromManifest: WaitTimeoutSec for EXE entries (lib v2.5)
UDC_Setup.exe is a WiX Burn bootstrapper that installs the underlying
MSI cleanly with /quiet but the wrapper process never exits - waits on
a bundled child service that doesn't return control. Empirically:
DisplayVersion=1.0.34 + UDC.exe present in C:\Program Files\UDC after
~30s, but Process.WaitForExit blocks indefinitely (>5 min observed).

EXE handler now honors optional WaitTimeoutSec on the manifest entry.
After timeout, kills the wrapper, re-runs Test-Installed; if detection
passes, treats as success (rc 0); if fail, surfaces as -2 (distinct
from -1 Process.Start failure). Default unset = old behavior (block
forever via WaitForExit) so existing entries unaffected.

Pairs with UDC manifest entry update on the v2 share:
  InstallArgs:    "West Jefferson" 9999  ->  /quiet /norestart
  + WaitTimeoutSec: 120

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 14:27:23 -04:00
cproudlock
f2123f268e ntlars-backups: switch FMSHostPrimary to WJFMS3.APPS.WLM.GEAEROSPACE.NET
Per user empirical confirmation 2026-05-04: the 31-char FQDN works on a
freshly-imaged production PC. Earlier failed attempts that surfaced as
"buffer too small" symptoms were actually a typo (WILM vs WLM) reaching
a non-resolvable hostname rather than a true reg-read buffer overflow.

The 20-byte buffer cap I observed in the Ghidra decomp of CPreScan +
CDoPersonnel may apply to a different code path, or the deployed prod
binary has a larger buffer than the VM-extracted MSI (DncMain.exe SHA
F8BFC2574288FD08ACBE6BC0D97E80A9C6EF57E2EB222F0CE752C2DC15F12223)
copy. Empirical evidence wins.

Sweep covers all 147 per-bay .reg files. FMSHostSecondary unchanged at
10.233.112.158 (IP literal still bypasses gethostbyname via inet_addr).

v2 share local mirror at
/home/camp/pxe-images/tsgwp00525-v2/shared/dt/shopfloor/main/ntlars-backups/
also updated (not in repo). Plus the FMS Primary host drift-catcher
RegValue in gea-shopfloor-{collections,nocollections}/manifest.json
and the hosts pin in common/scripts/Set-FmsHostsEntry.ps1 - all on the
v2 mirror, propagate to prod via SYNC-TO-PROD.md robocopy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:54:06 -04:00
cproudlock
3cb79715bf GE-Enforce: bump enforcerVersion to 2.4 in status.json write-back
Hardcoded version string surfaced in _outputs/logs/<host>/status.json
that the fleet check-in dashboard reads. Bump aligned with
Install-FromManifest lib v2.4 (PCTypes alias map + network-share EXE
staging). Lets the convergence-check oneliner distinguish PCs that
have picked up the post-rename dispatcher from those still on 2.0.

When pushed to share + the self-update common/manifest.json entry's
DetectionValue is bumped to the new SHA256 (commit notes record the
hash but the manifest itself lives on the v2 share, not in this repo
by design), every fleet PC's next cycle re-fires the self-update,
copies new bytes locally, the cycle after that writes status.json
with enforcerVersion=2.4. Fully visible in the dashboard read.

new GE-Enforce.ps1 SHA256:
  C8C14CFCE539ACDC6D16B31D2C456A5239516BDFA1EBFC820794A2D58BA7D9AC

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:39:40 -04:00
cproudlock
5a0243dd9c 00-PreInstall-MachineApps: PCTypes filter alias-aware
Pairs with the rename reorg's other alias maps (Install-FromManifest,
GE-Enforce, Get-PCProfile, verify-state). Fleet PCs whose pc-type.txt
becomes a new gea-shopfloor-* string still match legacy preinstall.json
PCTypes filters like ["Standard"], ["CMM"]. Same map shape as the
others - extract to a shared lib later if drift becomes a problem.

Without this, a freshly-imaged PC writing pc-type.txt =
gea-shopfloor-collections would skip every preinstall.json entry whose
PCTypes is Standard/CMM/etc - imaging chain installs nothing past common
apps with PCTypes=*.

Deployed to PXE server at /srv/samba/enrollment/shopfloor-setup/Shopfloor/
2026-05-04 alongside the rest of the renamed shopfloor-setup tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:11:27 -04:00
cproudlock
ce3fbf5a28 sweep: pre-existing drift + matrix UDC entry + ignore 142MB EXE
Bundles drift left uncommitted from prior sessions and the UDC matrix
verify entry added today.

Drift items (all per session-progress.md, completed in earlier sessions
but never staged):

- playbook/check-bios.cmd (deleted, moved to BIOS/check-bios.cmd)
- playbook/migrate-to-wifi.ps1 (made no-op 2026-04-24 after the dnsmasq
  no-gateway fix removed the wired-NIC race that motivated it)
- playbook/preinstall/oracle/Install-Oracle11r2.cmd (post-OUI .ora copy
  added 2026-04-24)
- playbook/preinstall/oracle/tnsnames.ora (live tnsnames, 469 KB,
  deployed alongside the wrapper 2026-04-24)
- playbook/pxe_server_setup.yml (dnsmasq dhcp-option=3,6 commented,
  Oracle .ora deploy task added 2026-04-24)
- playbook/shopfloor-setup/BIOS/{check-bios.cmd, models.txt} (BIOS
  detection refinements)
- playbook/shopfloor-setup/Shopfloor/Force-Lockdown.bat
- playbook/shopfloor-setup/Shopfloor/Monitor-IntuneProgress.ps1
- playbook/shopfloor-setup/Shopfloor/SetShopfloorAutoLogon.bat (new)
- playbook/shopfloor-setup/Shopfloor/09-Install-PrinterInstallerMap.ps1
  (new, places PrinterInstallerMap.exe + Public Desktop shortcut at
  imaging time; manifest entry self-heals on tamper)
- playbook/shopfloor-setup/Shopfloor/lib/Show-IntuneDeviceQR.ps1 (new,
  standalone QR rendering for site that wanted just that piece)
- playbook/shopfloor-setup/gea-shopfloor-collections/{Install-eMxInfo.cmd.template,
  Restore-UDCData.ps1} (these were uncommitted in pre-rename Standard/;
  git mv didn't catch them because they were untracked at the time)
- docs/shopfloor-machine-imaging-guide.md (operator-facing how-to)

Matrix:
- common.test/matrix.json: add UDC verify entry to gea-shopfloor-collections
  row. Surfaces UDC silent-install issue (item H pending) instead of
  letting it pass silently.

.gitignore:
- PrinterInstallerMap.exe (142 MB) excluded. Track via LFS or stage on
  PXE server only - too big for regular git history. Untouched on disk
  so existing local copy still works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:49:43 -04:00
cproudlock
64169819b3 Install-FromManifest: stage network-share EXE to local before invoking
Lib v2.4. Process.Start of an EXE that lives on a network share fails
with "Access is denied" when the dispatcher runs as SYSTEM, even when
the share is properly mounted via cmdkey + net use. Empirically
confirmed 2026-05-02 with UDC_Setup.exe via qga.

Fix: when the resolved EXE path is on a UNC or PSDrive-with-DisplayRoot
mount, copy the file into a per-cycle temp dir under $env:TEMP and run
from there. Cleanup happens in finally regardless of run outcome.
Cost is one transit per fire, which is rare in practice because most
EXE entries skip on subsequent cycles via DetectionMethod.

Validated on win11 VM with UDC_Setup.exe: dispatcher previously
returned blank exit code with "Access is denied" in stderr; now logs
"staged network EXE -> C:\WINDOWS\TEMP\ge-enforce-exe-..." and the
process runs to Exit 0 in ~18 seconds. UDC's separate "exit 0 without
actually installing" issue is a wrong-silent-flag in InstallArgs, not
this dispatcher fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:42:33 -04:00
cproudlock
6dcf96e40a Phase 3+4 rename reorg: repo dir renames + startnet.cmd menu
Pairs with Phase 1+2 from earlier (alias maps in Install-FromManifest,
GE-Enforce, Get-PCProfile, verify-state). See project-shopfloor-rename-reorg
memory for the plan.

Phase 3 (repo + paths):
- git mv per-PC-type dirs to gea-shopfloor-* names:
    Standard      -> gea-shopfloor-collections
    CMM           -> gea-shopfloor-cmm
    Keyence       -> gea-shopfloor-keyence
    Genspect      -> gea-shopfloor-genspect
    WaxAndTrace   -> gea-shopfloor-waxtrace
    Display       -> gea-shopfloor-display
    Lab           -> gea-shopfloor-common (folded; Timeclock+Lab merge)
- New gea-shopfloor-nocollections/ (clone of collections sans UDC scripts).
- New gea-shopfloor-heattreat/ (placeholder, README only).
- Move Standard/ntlars-backups/ -> _ntlars-backups/ (per-MN, not per-type).
- Run-ShopfloorSetup.ps1: Resolve-PCTypeDir helper walks alias group when
  the on-disk dir for the current pcType is missing. Set-MachineNumber
  helper-copy gated on collections|nocollections|legacy Standard-Machine.
- Update-MachineNumber.ps1: pcProfiles lookups try gea-shopfloor-collections
  first, fall back to legacy Standard-Machine. PowerShell 5.1 compatible
  (no null-coalesce).

Phase 4 (startnet.cmd menu):
- Choice 3 "GEA Shopfloor" now drills into a 9-item sub-menu instead of
  going straight to enrollment. Sub-cats:
    1. Machine with Collections        -> gea-shopfloor-collections
    2. Machine without Collections     -> gea-shopfloor-nocollections
    3. Common (Timeclock, Lab)         -> gea-shopfloor-common
    4. Keyence                         -> gea-shopfloor-keyence
    5. CMM                             -> gea-shopfloor-cmm
    6. Genspect                        -> gea-shopfloor-genspect
    7. Heattreat                       -> gea-shopfloor-heattreat
    8. Wax and Trace                   -> gea-shopfloor-waxtrace
    9. Display                         -> gea-shopfloor-display
- Office menu (existing 6-option) follows for every sub-cat.
- Machine number prompt only for collections + nocollections.
- pc-subtype.txt + display-type.txt no longer written. PCTYPE is a
  single full string (gea-shopfloor-*); subtype-aware code paths fall
  back to empty and resolve via the alias map.
- CMM bootstrap stage gate switched from "%PCTYPE%"=="CMM" to
  "%PCTYPE%"=="gea-shopfloor-cmm".

Test harness:
- B-enforce/run.sh PCSUBTYPE default changed from "Machine" to "" so
  single-arg invocation matches the new single-string scheme. Two-arg
  legacy form ("Standard Machine") still works via aliasing.
- B-enforce/tamper.ps1 alias-aware Test-MatrixEntryMatches mirroring
  verify-state.ps1.

Smoke-tested on win11 VM as SYSTEM via qga: B-enforce harness 5-phase
cycle (stage / baseline / tamper / heal / idempotent) passes 10/10
with PCType=gea-shopfloor-collections AND with legacy "Standard Machine"
two-arg form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:09:16 -04:00
cproudlock
48e20a7e73 SCOPE.md: note rename reorg in progress, alias maps live
Phase 7 of the gea-shopfloor-* rename. SCOPE.md keeps legacy names
("Standard-Machine", "CMM", "Lab", etc.) as primary keys for now;
new names accepted via alias maps in 4 places: Install-FromManifest,
GE-Enforce, Get-PCProfile, verify-state. See project-shopfloor-rename-reorg
memory for full plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:30:57 -04:00
cproudlock
c890e5b46c test harness + Get-PCProfile: alias-aware lookups for rename reorg
Phase 5 + 6 of the gea-shopfloor-* rename.

Get-PCProfile.ps1: when the legacy profileKey ("Standard-Machine",
"CMM", etc.) is missing from siteConfig.pcProfiles, walks the alias
group and returns the first matching new key ("gea-shopfloor-collections",
"gea-shopfloor-cmm", etc.). Vice versa: a fleet PC writing the new
string finds its profile under the old key. Same alias map shape as
GE-Enforce + Install-FromManifest, kept in sync manually for now -
extract to shared file later if drift becomes a problem.

matrix.json: adds 3 new rows for gea-shopfloor-nocollections,
gea-shopfloor-common (Timeclock+Lab merge), gea-shopfloor-heattreat
(placeholder). Existing rows for legacy names retained; the new
verify-state alias resolution lets either be requested.

verify-state.ps1: Test-MatrixEntryMatches walks the alias map so
harness invocation with "Standard Machine" or "gea-shopfloor-collections"
both resolve to the same matrix row.

Smoke-tested via qga-as-SYSTEM on win11: legacy Standard/Machine,
new gea-shopfloor-collections, and new gea-shopfloor-nocollections
all return 10/10 pass against current VM state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:29:32 -04:00
cproudlock
285d81edc4 GE-Enforce: alias-aware manifest dir resolution
Phase 2 of the gea-shopfloor-* rename. Pairs with the v2 share manifest
dir renames done in /home/camp/pxe-images/tsgwp00525-v2/ this session
(local-only, syncs to prod separately):

  standard-machine    -> gea-shopfloor-collections
  cmm                 -> gea-shopfloor-cmm
  keyence             -> gea-shopfloor-keyence
  genspect            -> gea-shopfloor-genspect
  waxandtrace         -> gea-shopfloor-waxtrace
  display             -> gea-shopfloor-display
  lab                 -> merged into gea-shopfloor-common
  (new)               -> gea-shopfloor-nocollections (clone of collections w/o UDC)
  (new)               -> gea-shopfloor-heattreat (placeholder)
  (new)               -> gea-shopfloor-common (Timeclock + Lab merge)

GE-Enforce now walks an alias group when the constructed dir name has
no manifest.json. Fleet PCs whose pc-type.txt still says "Standard" /
sub "Machine" continue to find their manifest at the new
gea-shopfloor-collections location, so the rename is invisible to them.
After Phase 4 (startnet.cmd) lands and freshly-imaged PCs write the new
strings directly, the alias resolution still works for both forms.

Smoke-tested on win11 VM as SYSTEM via qga: legacy Standard/Machine
and new gea-shopfloor-collections both reach the same manifest, fire
the same entries, complete cleanly.

Phases 3+4 (repo folder renames + startnet.cmd menu) deferred per
project-shopfloor-rename-reorg memory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:27:29 -04:00
cproudlock
5fe7e7767f Install-FromManifest: PCTypes alias map for rename reorg
Phase 1 of the gea-shopfloor-* rename per project-shopfloor-rename-reorg.
Manifests can use either old names (Standard, Standard-Machine, CMM,
Keyence, etc.) or new names (gea-shopfloor-collections,
gea-shopfloor-cmm, gea-shopfloor-keyence, etc.) interchangeably.

Equivalence sets defined inline. Each set is a list of names that all
match the same identity. The match logic resolves the current PC's
identity AND each PCTypes entry into their alias sets, then matches
if the sets intersect.

Standard maps to all three new shopfloor variants (collections,
nocollections, common) so an existing PCTypes=['Standard'] manifest
entry still applies when PC pc-type.txt becomes any of the three.
Standard-Machine maps to (collections, nocollections) only since
Timeclock subtype is now collapsed under common.

Smoke-tested on win11 VM as SYSTEM via qga: dispatcher run with
PCType='gea-shopfloor-collections' against the existing common
manifest (Standard-only PCTypes filters) fires Oracle / FMS hosts pin
correctly. Same run with PCType='Standard' PCSubType='Machine' fires
identically.

Phases 3+4 (repo folder renames + startnet.cmd menu reorg) deferred to
the next session - high breakage risk, must ship atomically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:15:57 -04:00
cproudlock
395d045cdf test harness: extend matrix to all 9 PC types
Adds rows for Standard-Timeclock, CMM, Keyence, Lab, WaxAndTrace,
Genspect, Display, Shopfloor alongside the existing Standard-Machine.
Per-type apps verified against the corresponding v2 manifest's detection
methods (PC-DMIS 2016/2019R2/Protect Viewer/CLM/goCMM for CMM;
VR-6000/USB driver for Keyence; kiosk shortcut for Display).

Common app list deduped via "$ref": "common.<key>" pattern. Verifier
resolves refs into the per-type apps array at runtime so each row stays
short and PCTypes-filter-aware (Lab + Display + Shopfloor get fewer
common apps because the manifest's PCTypes filter excludes them from
FMS hosts pin / Oracle / OpenText respectively).

verify-state.ps1 changes:
- $ref resolution against the matrix.common namespace
- Registry method now permits no DetectionName (key-existence only,
  e.g. Protect Viewer)
- New PnpUtilGrep method for INF-driver checks (Keyence USB driver)

Smoke-verified end-to-end on the win11 VM as SYSTEM via qga - 60 checks
across 9 PC types. Type-specific failures (5 CMM, 2 Keyence, 1 Display)
correctly surface "no payload staged" rather than masking it as pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:58:26 -04:00
cproudlock
b4e5152471 test harness: Path A (imaging chain) for Standard-Machine
Smokes end-to-end on the win11 VM in ~14s for Standard/Machine: 11/11
stage scripts exit 0 (6 Shopfloor baseline + 5 Standard per-PC-type),
transcripts land in C:\Logs\SFLD\ as expected.

Pieces:

- stage-image.ps1 - VM-side: clean prior state, robocopy shopfloor-setup
  tree from samba share to C:\Enrollment\shopfloor-setup, drop pc-type +
  pc-subtype + site-config, walk numbered stage scripts (^[0-9]{2}-) in
  Shopfloor/ then <PCType>/, run each, collect rc + summary. Skips PPKG /
  sync_intune / reboot - real machine identity is not touched.
- A-imaging/run.sh - host orchestrator: revert, stage repo tree to
  /home/camp/pxe-images/test-stage-A, mount Z: in VM as SYSTEM, invoke
  stage-image.ps1 with PCType/PCSubType params, collect transcripts.
  Optional PREINSTALL_PATH env if you have the binary installer payload
  available; default skips it (00-PreInstall logs "installer not found"
  for every entry, expected for orchestration-only test - per-app installs
  are covered by Path B).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:50:02 -04:00
cproudlock
eaf2dbf167 test harness: smoke-pass B-enforce, fix four issues
Harness now passes 9/9 across baseline + heal + idempotent phases on the
win11 VM (Standard/Machine), with 6 drift scenarios applied + healed
between the baseline and heal cycles in ~30s total.

Fixes:

1. lib/qga-run.py - extracted the qga round-trip out of an inline
   `python3 - <<PY` heredoc. The inline form clobbered stdin (heredoc
   replaces stdin to feed python the script, leaving sys.stdin empty
   for the PowerShell snippet the function caller piped in).
2. lib/qga.sh - dropped `set -euo pipefail`. When sourced, it leaked
   into the harness shell. Then any captured `out=$(qga_run_ps ...)`
   that exited non-zero (verify-state.ps1 returns 1 on any FAIL,
   normal during drift phases) would silently abort the harness.
   Callers handle non-zero with `|| rc=$?`.
3. B-enforce/run.sh do_verify - rewritten to capture rc, parse summary
   line, distinguish expect_pass=true vs false, route to ok / fail
   helper without aborting the harness on a normal non-zero verify.
4. matrix.json WJF Defect Tracker entry - switched detection from File
   to Registry (uninstall key DisplayVersion). The MSI does not drop
   the Defect_Tracker.exe artifact at the documented path even though
   the manifest's File detection treats it as installed; the uninstall
   reg entry is the reliable install marker. v2 manifest's File
   detection path may also need fixing, separate task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:45:06 -04:00
cproudlock
db1cdf7aee test harness: Path B (manifest-engine) for Standard-Machine
Initial harness scaffolding per SCOPE.md. Drives the win11 analyzer VM
via qemu-guest-agent (runs as NT AUTHORITY\SYSTEM, same context as
GE-Enforce in production - see reference-vm-qga-as-system memory note
for why this is preferred over WinRM).

Pieces:

- lib/qga.sh - host-side helpers (qga round-trip, snapshot revert,
  share mount via cmdkey + net use, file upload). Source from any
  harness script.
- lib/verify-state.ps1 - VM-side detection runner. Parses matrix.json,
  walks each app's verify block, prints PASS/FAIL with detail, exits
  0 only if every check passes. Methods: Registry, File, FileVersion,
  Hash, FileGrep.
- matrix.json - PC-type matrix data. Currently only Standard/Machine
  rows populated (apps + drift scenarios). Extending to other PC types
  is just adding rows.
- B-enforce/run.sh - 5-phase orchestrator (stage / baseline / tamper /
  heal / idempotent). Defaults to Standard/Machine. SKIP_REVERT=1 for
  faster iteration without burning the snapshot revert.
- B-enforce/tamper.ps1 - applies driftScenarios from matrix.json.
  Methods: RegRemove, RegSet, FileDelete, FileOverwrite, FileGrepDelete.

Path A (imaging-time install) and remaining 8 PC-type rows are next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:15:37 -04:00
cproudlock
26bc1720af Add SCOPE.md for shopfloor test harness
Two test paths: (A) imaging-time install via PXE preinstall +
Run-ShopfloorSetup.ps1 per PC type, (B) manifest-engine ongoing
enforcement via GE-Enforce + Install-FromManifest against the v2 share.

Locks the matrix before harness code lands: 9 PC-type rows, expected
install state per type, drift scenarios per app for Path B's
tamper+heal cycle. Decisions: skip JSON CI report (air-gapped solo
workflow), interactive stdout + exit 0/1 only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:57:59 -04:00
cproudlock
df443d5d28 ntlars-backups: normalize all 147 per-bay .reg files to wjfms3 + IP secondary
FMSHostPrimary  -> wjfms3.ae.ge.com  (was a mix of WJFMS3, wjfms3.ae.ge.com,
                   WJFMS3.ae.ge.com, WJFMS3.AE.GE.COM across 147 bays)
FMSHostSecondary -> 10.233.112.158   (was a mix of WJFMS3/4 short + .ae.ge.com)

Reasoning: eDNC's CPreScan + DNCdll CDoPersonnel resolve FMS hosts via MFC
CSocket, which calls inet_addr first then gethostbyname. Modern getaddrinfo
(used by PowerShell / Resolve-DnsName) succeeds on the GE corporate net for
this FQDN, but the legacy gethostbyname path does not - eDNC sits there
unable to resolve. Pinning the secondary to a dotted IP makes inet_addr
succeed before any gethostbyname is attempted, so the secondary connect
always works regardless of resolver state. Primary stays as FQDN so the
hosts file pin (added in a separate change to common/manifest.json) gives
gethostbyname an immediate hit. Both values fit the 20-byte buffer cap
that CPreScan + CDoPersonnel use when reading FMSHost* from registry.

Per-bay backups are consumed by Update-MachineNumber's Import-EDncRegBackup
at imaging time, so freshly-imaged PCs land with correct values. Existing
PCs are healed by the matching Type=Registry drift-catcher entries in the
v2 standard-machine manifest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:57:50 -04:00
cproudlock
7901cd9731 Setup-OpenText: Resolve-Path on SourceDir to flatten the CMD shim's "..\..\apps\opentext"
The shim Setup-OpenText.cmd hands "%~dp0..\apps\opentext" to the PS1.
PowerShell's Join-Path leaves the literal ".." segment in the path it
passes to msiexec, and the Windows Installer service rejects the package
with 1619 (ERROR_INSTALL_PACKAGE_OPEN_FAILED) for that reason. Every
other API resolved the path fine, masking the issue. Resolving SourceDir
once at script entry collapses ".." so the downstream msiexec /i and /p
calls receive a clean drive-rooted path.

Verified end-to-end on the win11 VM via the GE-Enforce dispatcher: msiexec
/i and /p both return 3010 (treated as success), profiles + shortcuts +
marker land cleanly, total 36s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:57:34 -04:00
cproudlock
c2fef53543 GE-Enforce: prune *.log older than 30 days each cycle
Bounds growth of C:\Logs\Shopfloor (per-day enforce-YYYYMMDD.log files),
C:\Logs\SFLD (Start-Transcript -Append accumulates), and C:\Logs\Keyence.
Today's enforce log is never touched (LastWriteTime = now). Cheap flat
scan per cycle; logs only when something actually got pruned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:57:23 -04:00
cproudlock
d6015f0906 site-config: strip UDC/eDNC/NTLARS from global startupItems/taskbarPins/desktopApps
These apps are Standard-Machine-only. Their presence in the global fallback
list (used when a pcProfile doesn't override) was a footgun: any newly-added
PC type without an explicit pcProfile would inherit UDC. Standard-Machine's
own pcProfile already declares them, so removing from the global is a no-op
for current PC types and cleaner for future ones.

Global baseline now: Defect Tracker + WJ Shopfloor + Plant Apps + Edge.
Standard-Machine (Standard PC type with subtype Machine) keeps full UDC/
eDNC/NTLARS set as before.
2026-05-01 12:47:10 -04:00
cproudlock
42f6410d25 Revert Install-UDCWebServerConfig.cmd - v2 manifest Type=File handles natively, wrapper was redundant 2026-05-01 12:09:13 -04:00
cproudlock
e9fc284dcb Restore-UDCData: mount share with SFLD creds instead of raw UNC from SYSTEM
Symptom: every Restore-UDCData log entry showed bay-level files as 'absent'
even when they actually existed on the share - on a device where another
PC's run had successfully consumed and migrated the same backup. Endless
'no work this cycle' loop on the device that should have done the consume.

Cause: script ran as NT AUTHORITY\SYSTEM (manifest engine on logon).
SYSTEM authenticates to remote SMB as the COMPUTER ACCOUNT
(DOMAIN\HOSTNAME$), not as a user. The SFLD share's ACL grants top-level
enumeration to authenticated computers (so Test-Path on share root +
bay dir returned True) but file-level read only to the SFLD user. With
no explicit user creds, Test-Path on bay-level files returns False -
indistinguishable from 'file not found' - so the script silently logged
'absent' on files that actually exist. A different PC with proper creds
consumed bay 3207 first; ours kept polling forever.

Update-MachineNumber.ps1's branch already worked around this by calling
Mount-SFLDShare (Restore-EDncReg.ps1's helper that reads
HKLM:\SOFTWARE\GE\SFLD\Credentials\* and net-use's the share with the
SFLD user identity).

Fix: Restore-UDCData.ps1 now does the same. Replaces raw-UNC Test-Path
polling with Mount-SFLDShare, probes via the W: drive letter, and
unmounts on every exit path. If creds are missing in registry the script
fails fast with a clear ERROR rather than masquerading as 'no backup'.
2026-05-01 11:50:04 -04:00
cproudlock
a72db8af5e Add Install-UDCWebServerConfig.cmd for ongoing manifest enforcement
Wrapper invoked by Install-FromManifest.ps1 (Type=CMD) when Hash detection
on C:\ProgramData\UDC\udc_webserver_settings.json fails. Mirrors the
Install-eMxInfo.cmd pattern: copies the colocated json from the SFLD
machineapps share into ProgramData\UDC.

Manifest entry (with DetectionValue 4E04A865...DEA3) goes in
machineapps-manifest.json on the SFLD share - separate from this repo.
2026-04-30 12:58:00 -04:00
cproudlock
75b85bfde6 Update-MachineNumber: pull per-bay udc_settings.json from SFLD on placeholder->real
When the tech transitions a 9999-placeholder PC to its real machine number,
also restore the per-bay udc_settings_<num>.json from
\\tsgwp00525\shared\spc\udc\settings_backups\. PXE-time preinstall can't reach
this share (no SFLD creds yet), so 00-PreInstall uses the local C:\Enrollment
mirror; post-config the share is reachable, so the renumber path goes direct
to the canonical source.

Adds udcSettingsSharePath to site-config.json under Standard-Machine.

Bundles in prior uncommitted work in the same file: ntlars reg restore,
UDC data restore (CurrentData.json + ArchivedData/), MTConnect Devices.xml
inline rewrite + service restart, and one-shot consume of per-bay UDC
backup -> migrated/<timestamp>/.
2026-04-30 12:34:53 -04:00
cproudlock
6e9053b83c 00-PreInstall: pre-stage udc_webserver_settings.json + firewall/NetFx3 hardening
Add staging block that copies udc_webserver_settings.json from the enrollment
share to C:\ProgramData\UDC during preinstall, mirroring the existing
udc_settings.json pattern. New PCs were imaging without UDC web server
config because the file was never wired into the imaging flow (only the
remote-maintenance task in powershell/remote-execution touched it).

Also folds in two prior uncommitted hardening blocks in the same script:
firewall NotifyOnListen=False (suppress Oracle OUI's listen-port prompt)
and NetFx3 pre-enable (Oracle 11.2's welcome path needs .NET 3.5).
2026-04-30 12:16:41 -04:00
cproudlock
4f4f1f43e8 Restore-UDCData: handle ArchivedData-only backups (no CurrentData.json)
Production case: bay 3207 had ArchivedData\ on the share with full
production records but no CurrentData.json at the bay root. The previous
Restore logic treated CurrentData.json as the marker for "valid backup"
and exited early when absent, so the script silently no-op'd every cycle
even though there was real archive data ready to restore.

Asymmetric with Backup-UDCData.ps1, which already handles missing
CurrentData.json gracefully (it copies whatever exists). Possible causes
of CurrentData.json absence in a backup: source PC had no live UDC
session at backup time (UDC inactive / not recording), backup partially
failed for that one file (no Backup-side log to confirm without rerun).

Either way, an ArchivedData-only backup is still a valid backup.

Behavior change:
- Early-exit only when BOTH CurrentData.json AND ArchivedData\ are
  absent. Otherwise proceed with whatever exists.
- Copy step for CurrentData.json wrapped in srcCurExists guard.
- consumeOk now requires: every present source successfully copied,
  AND at least one thing was actually copied.
- Move-to-migrated wraps CurrentData.json move in Test-Path guard
  (was already guarded for ArchivedData).
- restore.manifest.json gains CurrentDataPresent and ArchivedDataPresent
  booleans so future audits can see which side actually restored.
- UDC relaunch now fires when EITHER copy succeeded (was only on
  CurrentData.json copy).

Verbose logs now distinguish three cases at the early-exit:
- Both absent: "no work to do this cycle" (the 99% path)
- Only ArchivedData\: WARN with explanation, proceed
- Only CurrentData.json: WARN with explanation, proceed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:49:38 -04:00
cproudlock
7be5518fd7 Fix v2 imaging: copy common/ at imaging time + use $setupDir not $PSScriptRoot
Two bugs that have been silently masking GE-Enforce registration since
Stage 2a landed 2026-04-22, surfaced when v1 enforcers were retired
(commit 0badfc1) and could no longer cover for the missing v2 registration.

Bug 1: startnet.cmd at imaging time only xcopied Shopfloor\ and the
PCTYPE-specific dir from the imaging share to W:\Enrollment\shopfloor-setup\.
common\ was never copied. v1 dispatchers lived per-pctype and rode in via
the %PCTYPE% xcopy, so this was never noticed. v2's GE-Enforce.ps1 +
Register-GEEnforce.ps1 + lib\Install-FromManifest.ps1 all live in common\
and got skipped at imaging entirely.

Fix: add a third xcopy block for common\, mirroring the Shopfloor\ block
above it. Applies to playbook/startnet.cmd and startnet-template.cmd.

Bug 2: Run-ShopfloorSetup.ps1 line 288 set $commonSetupDir via
'Join-Path $PSScriptRoot common'. Run-ShopfloorSetup.ps1 lives at
C:\Enrollment\Run-ShopfloorSetup.ps1 (xcopied by startnet.cmd), so
$PSScriptRoot resolves to C:\Enrollment, and $commonSetupDir resolved
to C:\Enrollment\common - which is NOT where common\ lives even after
the bug 1 fix (correct path is C:\Enrollment\shopfloor-setup\common\).
The Test-Path -LiteralPath check on Register-GEEnforce.ps1 returned
false silently and GE-Enforce never registered.

Same bug existed for Register-MapSfldShare on line 321.

Fix: $PSScriptRoot -> $setupDir for both. $setupDir was already defined
on line 51 as Join-Path $enrollDir "shopfloor-setup", which is the path
the rest of the script uses consistently.

Pre-v1-cleanup, v1's per-pctype enforcer registrations on lines 322-357
(now deleted) ran independently and covered the gap, so PCs ended up
with v1 enforcers and the user thought v2 was running. Post-cleanup,
this bug means nothing gets registered.

PXE server has been patched directly: boot.wim re-baked with the new
startnet.cmd, /srv/samba/enrollment/shopfloor-setup/Run-ShopfloorSetup.ps1
replaced. New PXE-imaged PCs from this point forward will register
GE-Enforce correctly.

For PCs imaged before this fix: run Deploy-GEEnforce.ps1 from the SFLD
share's _meta/runtime/ to retrofit. Same one-liner used for promoting
v1 PCs to v2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:31:15 -04:00
cproudlock
26ecd0da0a 01-eDNC.ps1: match both eDNC-*.msi and eDNC_*.msi naming styles
Vendor stamps the 6.4.5 MSI as eDNC_6-4-5.msi (underscore + hyphens) but
prior versions shipped as eDNC-6.4.3.msi (dash). The previous filter only
matched the dash form so the imaging-time install would skip 6.4.5 outright.
Filter is now eDNC*.msi which catches both. Imaging dir is expected to hold
exactly one version at a time; rollback to a prior version is handled
post-imaging via the SFLD share's standard-machine/apps/ alternates, not by
keeping multiple MSIs in the imaging path.

Also updated the Write-Warning fallback to mention the new filter pattern.

PXE server's /srv/samba/enrollment/shopfloor-setup/Standard/eDNC/ has been
swapped 6.4.3 -> 6.4.5 alongside this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:45:38 -04:00
cproudlock
6b3690e286 Restore-UDCData: verbose per-cycle logging + share-reachability retry
Two production-debuggability gaps closed.

1. Logging is now always-on. The previous version exited silently on the
   common no-op paths (no UDC installed, no backup waiting, share not
   reachable), leaving zero log evidence when techs reported "restore
   didn't happen". New behavior writes a header + identity + share-path
   + decision-point line to a single rotating log file every cycle.
   Errors include exception type, position, and full ScriptStackTrace.
   Log lives at C:\Logs\UDC\Restore-UDCData.log with a 1 MB cap and
   one-generation rotation to .old.log.

2. Share-reachability is now polled instead of probed once. The SFLD
   share over the SMB redirector takes 20-60 s to become reachable
   from SYSTEM context after a cold logon, especially on the first
   GE-Enforce cycle of the boot. The old single Test-Path returned
   false in that window and the script silently exited, missing the
   backup. New behavior polls Test-Path on the share root every 3 s
   for up to 60 s (both tunable via -ShareTimeoutSec / -SharePollSec)
   before deciding "no backup". If the share never comes up in that
   window the script exits 1 instead of 0 so the dispatcher logs a
   visible failure.

Both behaviors propagated to the host staging copy at
/home/camp/pxe-images/Restore-UDCData.ps1 and to the v2 share-staged
copy at tsgwp00525-v2/.../standard-machine/scripts/Restore-UDCData.ps1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:49:04 -04:00
cproudlock
e169f8d0f5 Standard-Machine: UDC backup/restore use ArchivedData (not ArchiveData)
UDC's per-bay archive directory is C:\ProgramData\UDC\ArchivedData, not
ArchiveData. The previous spelling was a typo introduced when the scripts
were first written; it would have meant Backup-UDCData.ps1 found no archive
content (silent zero-file backups), and Restore-UDCData.ps1 wrote into a
location UDC does not read from.

Path swap is straight string replacement across both scripts plus the .bat
wrapper's usage comment. Manifest field names in backup.manifest.json /
restore.manifest.json (ArchivedDataPresent, ArchivedDataFiles,
ArchivedDataBytes) updated to match.

Update-MachineNumber.ps1's parallel UDC-restore branch (still uncommitted
in a prior workstream) has the same fix in the working tree, captured in
that branch's eventual commit.

The v2 share-staged copy at tsgwp00525-v2\standard-machine\scripts\
Restore-UDCData.ps1 also got the fix and is ready for push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:45:39 -04:00
cproudlock
0badfc1983 Retire v1 per-pctype enforcers; GE-Enforce is the sole dispatcher
Stage 2a (GE-Enforce.ps1, landed 2026-04-22) is now the only ongoing-update
enforcer. The legacy per-pctype tasks (Machine-Enforce, Common-Enforce,
CMM-Enforce, Keyence-Enforce, Acrobat-Enforce) were kept as transition
belt-and-suspenders; with retrofitted PCs handled, the v1 path is dead and
gets removed entirely.

Deleted (13 files):
  Standard/{Machine-Enforce,Register-MachineEnforce}.ps1
  Standard/machineapps-manifest.template.json
  common/{Common-Enforce,Acrobat-Enforce,Register-CommonEnforce,Register-AcrobatEnforce}.ps1
  common/common-apps-manifest.template.json
  CMM/CMM-Enforce.ps1
  Keyence/Keyence-Enforce.ps1
  {CMM,Keyence,Standard}/lib/Install-FromManifest.ps1 (orphan dups of common/lib)

Trimmed:
  Run-ShopfloorSetup.ps1: dropped the legacy register-* invocations (Common,
    Machine) and the transition-period comment. Sole enforcer registration
    is now Register-GEEnforce.
  09-Setup-Keyence.ps1: keeps imaging-time install (step 1); removes the
    enforcer staging (step 2) and scheduled-task registration (step 3).
    Library lookup repointed to common/lib/Install-FromManifest.ps1.
  09-Setup-CMM.ps1: same treatment - keeps .NET 3.5 enable, install,
    PC-DMIS ACL grants, and bootstrap cleanup. Library repointed to common/lib.
  cmm-manifest.json + keyence-manifest.json: _comment fields updated to
    reflect imaging-time-only role (ongoing enforcement now goes through
    the v2 share manifests via GE-Enforce).

Verified clean: no orphan references to *-Enforce.ps1 / Register-*Enforce.ps1
/ machineapps-manifest / common-apps-manifest in any code path that runs.
A few historical mentions remain in unmodified header comments (GE-Enforce.ps1,
Deploy-GEEnforce.ps1, Monitor-IntuneProgress.ps1) describing what the new
dispatcher replaced; left as historical context.

Run-ShopfloorSetup.ps1 also picks up an unrelated 1-line hunk adding
SetShopfloorAutoLogon.bat to the desktop-copy list (already in the working
tree from a prior session). The file itself is not yet tracked; the
desktop-copy step is Test-Path-guarded so this is harmless until the
.bat is committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:55:40 -04:00
cproudlock
8564a37541 Standard-Machine: UDC data backup + restore scripts for PC swap workflow
Backup-UDCData.bat / Backup-UDCData.ps1: tech-runnable, UAC-self-elevating.
Run on the OLD PC before retirement; reads bay number from
udc_settings.json, copies CurrentData.json + ArchiveData/ to
\\tsgwp00525\...\backup\udc\<bay>\, drops backup.manifest.json. Refuses
the 9999 placeholder so backups never collide across PCs.

Restore-UDCData.ps1: idempotent, designed for the manifest engine. 99%
of cycles silent no-op (sub-second, zero side effects); 1% (cycle after
a backup lands at this PC's bay) restores files locally, moves consumed
backup to <bay>\migrated\<timestamp>\, writes restore.manifest.json,
relaunches UDC. Round-trip + no-op fast path verified end-to-end on the
win11 analyzer VM. Already wired into the Standard-Machine GE-Enforce
manifest at standard-machine\manifest.json on the v2 share.

Complementary to the placeholder-to-real branch in Update-MachineNumber.ps1:
that branch covers the 9999 -> real flow, this one covers the
pre-imaged-then-swapped flow where Update-MachineNumber already ran
before any backup existed. Both safely no-op if the other consumed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:27:20 -04:00
cproudlock
70f176650b Blancco: playbook now produces working Ubuntu-kernel initramfs out of the box
Companion to the previous commit (4550d43). Three files that should have
been in the same commit but got left out of `git add`:

- .gitignore: negate rule for boot-tools/blancco/grub-blancco.cfg so the
  tracked cfg (source of truth for grubx64.efi rebuilds) survives
  the blanket boot-tools/ ignore.

- playbook/blancco-init.sh: rewritten for modprobe-with-deps, full NIC
  driver coverage, set -x trace to /dev/console, dmesg + PCI-device +
  /proc/modules dump + interactive shell on "no NIC after 60s".
  Replaces the narrow insmod-loop version that silently hung on
  unsupported NICs.

- playbook/pxe_server_setup.yml "Build Blancco PXE initramfs" task now
  sweeps the full drivers/net/ tree (ethernet + phy + mdio + usb + fddi
  + wan) plus overlay / squashfs / loop / ptp / libphy / mii deps, runs
  depmod to regenerate modules.dep inside the initramfs (required for
  modprobe dependency resolution), and symlinks the full applet list
  blancco-init.sh needs (modprobe, insmod, dmesg, find, env, etc).
  Result: ~20 MB initramfs vs the old 2 MB narrow build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:08:57 -04:00
cproudlock
03ed694671 pxe_server_setup: close three playbook gaps from 2026-04-22 session
1. Deploy gea-standard / gea-engineer FlatUnattendW10.xml
   The playbook only copied the shopfloor variant before; standard and
   engineer's unattend was hand-staged on the running servers. New task
   loops the repo's playbook/FlatUnattendW10.xml into Deploy/ for each
   entry in standard_types (new var covering gea-standard, gea-engineer,
   ge-standard, ge-engineer). force: yes because repo drift vs deployed
   copy is what produced the Win10/Win11 search-cleanup regression
   earlier this session (d49f516).

2. Deploy Oracle Client 11.2 preinstall payload
   preinstall.json now leads with Oracle 11.2 (commit 3a29784). The CMD
   wrapper is tracked in the repo at playbook/preinstall/oracle/; the
   686 MB Oracle_OracleDatabase_11r2_V03.zip is too large to commit and
   rides on USB under oracle/ alongside BIOS exes. Three tasks:
   mkdir staging dir, copy CMD from usb_mount, copy zip from usb_root
   with a soft-fail + warning if absent.

3. No change needed for sync-preinstall.sh — Oracle 10.2.0.3 flat
   installer was already dropped in 9235d19.

YAML lints clean. Fresh server built from this commit will bring up
Blancco-agnostic imaging paths correctly; Blancco-specific gaps
(grubx64.efi native-vs-slim, narrow kexec-initrd driver tree,
narrow blancco-init.sh) are still deferred per earlier "option B"
decision and remain server-side-pinned only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:59:08 -04:00