Commit Graph

301 Commits

Author SHA1 Message Date
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
80e9c32fae Add GE-Enforce v2 architecture doc
Captures the full picture of how the manifest engine works, why scripts
don't need self-heal entries (run from share), credential context
(SYSTEM = computer account, requires Mount-SFLDShare for file-level
reads), C:\Enrollment vs SFLD share copy distinction, and update
workflows. Written in response to a session that wasted time adding
redundant manifest entries because this wasn't documented.

Companion to scripts/diagnostics/Capture-LockdownState.ps1 and the
auditing script in pxe-images/Audit-SFLDShare.ps1.
2026-05-01 12:15:31 -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
1ae5bdce57 Add three-stage imaging snapshot tool + runbook
scripts/diagnostics/Capture-LockdownState.ps1 captures Windows endpoint
state at three lifecycle checkpoints so the deltas isolate which phase
delivered (or failed to deliver) each component:
  - pre-category   - PPKG-enrolled, no Intune category yet
  - post-category  - category-driven assignments arrived, pre-lockdown
  - post-lockdown  - kiosk + autologon + AppLocker fully landed

Bumped from the previous 2-stage (pre/post) version. Legacy 'pre'/'post'
aliases preserved.

Captures additions driven by the SFLD-DSC v2.0.2 post-mortem:
  - IMECache file listing (catches missing sastoken.txt)
  - DSCDeployment.log + version.txt copied from C:\pc\
  - SFLD\DSC payload listing
  - C:\Logs\BPRT\ runtime state (criticalChecks.json, packageInfo.json)
  - C:\WCDApps\ deploy verification
  - Windows\Provisioning\Diagnostics copy
  - Tasks-RunHistory.csv with LastRunTime + LastTaskResult per task
  - DeviceManagement-Events.csv (MDM 429s, AAD token failures)
  - Provisioning-Events.csv (PPKG runtime errors)
  - MDM-Certificates.csv (enrollment cert health)

scripts/diagnostics/snapshot-runbook.txt: step-by-step ops guide
covering when to fire each stage, where output lands, how to ship it
back via image-upload share, and which files to compare first when
diffing.
2026-05-01 08:53:52 -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
4550d43d9d Blancco: lock in Ubuntu-kernel switch_root path in playbook + source of truth
Closes the "option B" gap left earlier in this session. Fresh PXE servers
built from this commit will boot Blancco identically to .1/.2 today
instead of reproducing the Apr-14 regression (commit d6776f7).

Three moving parts:

1. boot-tools/blancco/grub-blancco.cfg (new, tracked via .gitignore
   negation) carries the slim Ubuntu-kernel cmdline. prepare-boot-tools.sh
   already rebuilds grubx64.efi from this file via grub-mkstandalone, so
   binary doesn't need to be tracked. The server-side grub-blancco.cfg
   still has the native-kernel cmdline but it's orphaned - nothing in
   the active boot flow reads it.

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

3. 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's 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. Size is a
   non-issue (1-2 s HTTP load at gigabit) versus the hours of debugging
   the narrow build cost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:08:39 -04:00
cproudlock
712fa4fd3e download-drivers.py: strip CRLF line endings
Shebang was \`#!/usr/bin/env python3\r\` which errored with
\`/usr/bin/env: python3\r: No such file or directory\`. Pure line-ending
fix, no content change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:04:02 -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
cproudlock
d49f516b16 FlatUnattendW10: catch repo up to deployed state + Win10/Win11 search cleanup
Two things in one pass because the repo copy was 162 lines behind the
deployed one already:

1. Sync repo to the currently-deployed FlatUnattendW10.xml baseline
   (Java JRE 8 u441 + Java auto-update pins + Cortana/Bing/Search
   disable block that had been added on-server but never committed).

2. Prune three ineffective registry entries and replace the Bing
   suppression with a documented equivalent that works on both Win10
   and Win11:
   - DROP #32  HKLM\...\Search\CortanaEnabled=0
               Undocumented at HKLM (the real key is HKCU). No effect.
   - DROP #37  AllowCortanaAboveLock=0
               Deprecated per AboveLock Policy CSP. Cortana app was
               removed from Win11 in Canary 25967 anyway.
   - REPLACE #34  BingSearchEnabled (HKLM, undocumented) with
                  DisableSearchBoxSuggestions=1 written into the
                  Default User hive so every new account inherits it.
                  This is the Microsoft-documented kill-switch for
                  Bing / web results in Start-menu search on both
                  Win10 and Win11.

Validated XML well-formed (xmllint + Python ET). RunSynchronous orders
remain unique and ascending after the deletions. Deployed to both PXE
servers under /srv/samba/winpeapps/{gea-engineer,gea-standard}/Deploy/
with timestamped .pre-winsearch-cleanup-* backups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:55:16 -04:00
cproudlock
3a29784124 preinstall: add Oracle Client 11.2 as first application
Oracle 11.2 is now installed at image build time (preinstall) rather
than deferred to the runtime enforcer. eDNC / NTLARS / UDC / CMM tooling
all link against the Oracle home, so shipping an image without Oracle
means the first-boot experience is broken until the enforcer completes.

Uses the EXE-launches-CMD trick (same pattern as OpenText Setup-OpenText
.cmd) since the preinstall runner only knows MSI/EXE. The wrapper itself
accepts the zip next to the .cmd (preinstall flat layout) OR under
..\apps\ (SFLD share layout for the runtime enforcer), so one script
serves both paths. First entry in Applications[] so downstream apps see
Oracle already present.

The 686 MB Oracle_OracleDatabase_11r2_V03.zip lives outside git and is
pushed to /srv/samba/enrollment/pre-install/installers/oracle/ separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:19:45 -04:00
cproudlock
9235d19e55 preinstall: drop Oracle 10.2.0.3, defer to runtime enforcer for 11.2
10.2.0.3 was being installed into every Standard/CMM/Genspect/Keyence/
WaxAndTrace/Display image at build time. Oracle 11.2 is now installed
and version-enforced at runtime by the GE-Enforce common manifest
entry (Install-Oracle11r2.cmd), so baking 10.2 into the image creates
drift: 10.2 on disk, 11.2 expected by registry. Remove from preinstall.json
and drop the flat installer from sync-preinstall.sh so new builds come
up clean and the enforcer does the install on first boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:15:01 -04:00
cproudlock
f7a14f6804 blancco-preferences: disable wired LAN so WiFi INTERNETACCESS takes default route
The config.img internal preferences.xml has <network><enabled>false</enabled>
(set up by b7cd097 for native-kernel BMC licensing over WiFi). The sibling
playbook/blancco-preferences.xml was left at true, so the Ubuntu-kernel
switch_root path downloaded a preferences.xml that re-enabled wired LAN,
killing the WiFi default route and preventing BMC reach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:52:44 -04:00
cproudlock
c918dea9d1 Revert all Blancco changes from this session
User reports Blancco was working before our mirror/session activity
today - then my attempted fixes (grubx64.efi rebuild, kexec-initrd
driver sweep, verbose blancco-init.sh) made it worse:

  - First attempt (narrow igc driver add) did not help because the
    switch-root path was not the one actually loaded by grubx64.efi's
    embedded config.
  - Second attempt (swapped grub embedded config to Ubuntu-kernel path)
    got further, but then kexec-initrd modules failed on insmod.
  - Third attempt (full ethernet tree sweep) pulled in broken ancient
    drivers (winbond-840, w5100-spi, xirc2ps_cs) that failed with
    unknown-symbol errors and prevented good drivers from loading.

Full revert: .gitignore, blancco-init.sh, pxe_server_setup.yml back to
the pre-session commit 6dcf832 state. Removes boot-tools/blancco/grub-
blancco.cfg from git (it was only added this session).

Runtime on both PXE servers was also restored: grubx64.efi and
kexec-initrd.img reverted from the .bak files taken before each
modification this session.

Whatever was there before today is now restored byte-for-byte on both
servers. If there is still a Blancco boot issue on specific modern
hardware that the user needs to fix, we will diagnose that narrowly
against the actual failure mode on that specific machine, not by
making sweeping preemptive changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:33:49 -04:00
cproudlock
d7ec6a2b5f Blancco: sweep full NIC driver tree into kexec-initrd + verbose init
Previous approach listed ~6 specific drivers (e1000e, igb, tg3, bnx2,
bnxt_en, b44) and silenced insmod errors (2>/dev/null). On modern Dell
fleet (Latitude 5330/5440, Pro-series, newer OptiPlex) this missed
igc (Intel I225/I226) entirely, and for the drivers we did include,
dependency modules they need at insmod time (libeth, libie, dca,
i2c-algo-bit, macsec, mii, libphy, ptp, ...) were never bundled.
insmod does not resolve dependencies, so NIC drivers that need
helpers failed to load silently.

playbook/pxe_server_setup.yml (kexec-initrd build):
  - Sweep the whole drivers/net/ethernet tree (~170 drivers, all
    vendors, ~15 MB total). Drivers for hardware not present skip
    without binding.
  - Add common helper dirs: drivers/net/{phy,mdio}, drivers/i2c/algos,
    drivers/dca, drivers/ptp, net/macsec, drivers/ssb.
  - overlay.ko kept.

playbook/blancco-init.sh:
  - Load helpers BEFORE main NIC drivers (libeth/libie, dca,
    i2c-algo-bit, macsec, mii, ssb, libphy, mdio*, phy*, ptp*),
    then iterate remaining modules.
  - Remove 2>/dev/null on insmod so actual failures surface on the
    boot console.
  - Print kernel version + /sys/class/net before/after driver load,
    plus dmesg grep for NIC driver activity.
  - On "no interface found" failure, dump dmesg tail and drop to a
    busybox shell for manual debug rather than just hanging.

Separate from this commit but related: kexec-initrd.img on both PXE
servers (.1 and .2) was rebuilt inline with these changes. Pre-rebuild
binary kept as kexec-initrd.img.bak-<timestamp>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:29:35 -04:00
cproudlock
2ac88a6c1b Blancco: back to Ubuntu-kernel switch_root; track grub-blancco.cfg in git
Two changes in one:

1. grub-blancco.cfg reverts to booting vmlinuz-ubuntu + kexec-initrd.img
   (the switch_root path from commit dd2fec5). b7cd097 had switched
   back to Blancco's native vmlinuz-bde-linux via TFTP for "hardware
   compat," but that turned out to regress on modern Dell fleet
   hardware - Blancco's packaged kernel lacks igc (Intel I225/I226 on
   Latitude 5330/5440, Pro-series, newer OptiPlex). Symptom was
   successful TFTP fetch of kernel+initrd, then silence at the Blancco
   UI ("no network card found") because the rootfs HTTP fetch has no
   NIC to use.

   The switch_root initramfs built by the Ansible playbook now carries
   igc, bnxt_en, r8169, atlantic, and other modern NIC modules, so the
   Ubuntu kernel path reliably finds a working NIC across the whole
   fleet before HTTP-fetching airootfs.sfs and kexec'ing into Blancco.

   Native kernel still runs under Blancco's control after kexec - only
   the *loader* kernel changes.

   The grubx64.efi binary (embedding this config via grub-mkstandalone)
   was rebuilt and pushed to /var/www/html/blancco/ on both PXE servers
   (.1 and .2). Old binary kept as grubx64.efi.bak-<timestamp> on each.

2. .gitignore: negate boot-tools/blancco/grub-blancco.cfg so this one
   file is version-controlled. boot-tools/ is otherwise local cache
   artifacts (kernel/initrd/sfs extracted from Blancco ISO, grubx64.efi
   built output). grub-blancco.cfg is source-of-truth and must not be
   re-derived; any new PXE server build needs the exact same content
   or the Blancco boot chain regresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:23:09 -04:00
cproudlock
1886857c0f Use [Environment]::MachineName instead of $env:COMPUTERNAME
Live kernel NetBIOS name instead of the PowerShell process-env cache.

$env:COMPUTERNAME is populated when PowerShell starts and does not
update if the PC gets renamed (common on Intune-managed Autopilot /
AADJ devices that come up with a DESKTOP-XXXXXXXX name and get
renamed by policy post-imaging). Until the next reboot, the env var
stays stale while 'hostname.exe' already reports the new name.

That mismatch showed up live on the first production retrofit: the
status.json was written under _outputs/logs/DESKTOP-XXXXXXXX/
instead of under the device's current name, and the
TargetHostnames filter and monitor drift-check would likewise see
the stale name.

[Environment]::MachineName reads from the kernel on each call, so
it always returns the current NetBIOS name. Swapped at all five
callsites in GE-Enforce.ps1, Register-GEEnforce.ps1, and
Install-FromManifest.ps1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 12:51:05 -04:00
cproudlock
ba03f63465 Register-GEEnforce: use SHA-256 instead of MD5 for per-PC jitter offset
FIPS-enforced PCs (System cryptography GPO) reject non-approved
algorithms at the .NET crypto API level. MD5 throws
"This implementation is not part of the Windows Platform FIPS
validated cryptographic algorithms" on .Create(), which aborts
Register-GEEnforce before the scheduled task is built.

SHA-256 is FIPS 180-4 approved and its default .NET provider is
validated, so SHA256.Create() works under FIPS mode. Functionally
equivalent for the 0-4 minute modulo we need for jitter.

Hit this live on the first production retrofit. Enforcer runtime
files were copied and legacy tasks were unregistered, but the new
task creation aborted. Rerunning Deploy-GEEnforce.ps1 is idempotent
and recovers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 12:39:26 -04:00
cproudlock
70a36711c3 Install-FromManifest: InUseCheck ForceClose / CloseAndReopen
Adds partial Stage 2b support for InUseCheck entries in manifests. When
an entry declares InUseCheck.Behavior = ForceClose or CloseAndReopen and
the listed processes are running at install time, the lib now:

  1. Calls CloseMainWindow() on each matching Process handle (polite WM_CLOSE).
  2. Waits GracefulCloseTimeoutSec (default 10) for exit.
  3. Hard-kills the process if it did not exit gracefully.
  4. Proceeds with the install.

"CloseAndReopen" is currently treated the same as ForceClose - no reopen
happens today. Stage 2b will add the user-session scheduled-task trick
to relaunch the closed app in the logged-in user's session. In practice
for the 24/7 ShopFloor persistent-user pattern the operator relaunches
the app manually (or the app is registered as Startup and reopens on
the next reboot), which is acceptable.

Concrete impact: the eDNC entry in standard-machine/manifest.json lists
InUseCheck.Processes = DncMain + NTLARS with Behavior=CloseAndReopen. On
a retrofit or upgrade cycle that finds eDNC 6.4.3 needs to go to 6.4.5,
the lib now force-closes DncMain and NTLARS before msiexec rather than
risking Restart Manager silently scheduling a pending-file-replace that
does not actually upgrade until the next reboot (which on a 24/7 PC
might be never).

Verified in the Win11 analyzer VM against manifests declaring InUseCheck
on eDNC - logs show "InUseCheck: DncMain (PID ...) asked to close"
followed by either graceful exit or the force-kill path, then install
proceeds without 3010.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 12:17:34 -04:00
cproudlock
eb68793e79 Stage 2a: unified GE-Enforce framework + share-root mirror
Consolidates per-type enforcers (CMM, Keyence, Machine, Common, Acrobat)
into one dispatcher driven by pc-type.txt + site-config and a share-side
manifest layout. Same share is now the single source of truth for routine
software updates without re-imaging.

Runtime:
  common/GE-Enforce.ps1           SYSTEM scheduled task. Reads
                                   common/manifest.json plus optional
                                   <pcType>/manifest.json and
                                   <pcType-subType>/manifest.json.
                                   Dispatches each entry through the lib.
                                   Writes _outputs/logs/<hostname>/status.json
                                   on the share after each cycle for fleet
                                   monitoring.
  common/Register-GEEnforce.ps1   Task registration. Triggers: AtLogOn +
                                   every 5 min (jittered per-PC from
                                   hostname hash) + daily at 05:45,
                                   13:45, 21:45 EST shift windows.
                                   Unregisters legacy per-type tasks on
                                   install so the two coexist at most for
                                   the duration of a single enforce cycle.
  common/Deploy-GEEnforce.ps1     Retrofit helper for already-imaged PCs
                                   (admin-run; copies runtime + registers
                                   task + optional immediate trigger).

Library (common/lib/Install-FromManifest.ps1):
  - New Type values: PS1, BAT, File, Registry, INF
  - New DetectionMethod values: Always, MarkerFile, ValueMatches, pnputil
  - TargetHostnames filter (exact + -like wildcards, ANDed with PCTypes)
  - Schema version check (logs WARN on manifest newer than lib MAJOR)
  - Auto-writes MarkerFile on successful one-shot PS1/BAT/CMD runs
  - MSI log scan on failure surfaces meaningful install errors
  - Lib version bumped 2.0 -> 2.1 for TargetHostnames

Observability:
  common/monitor-fleet-status.py  Scans _outputs/logs/*/status.json for
                                   stale check-ins, failed scopes, and
                                   version drift. Respects scope (dir-name),
                                   PCTypes, and TargetHostnames filters so
                                   entries excluded from a PC do not
                                   false-flag as drift.

Regression harness:
  common/test/                    Parameterized VM harness + README
                                   covering every action type plus
                                   rollback, bad/missing SFLD creds, and
                                   schema versioning.

Imaging integration:
  Run-ShopfloorSetup.ps1 now stages GE-Enforce.ps1 and lib to
  C:\Program Files\GE\Shopfloor\ and invokes Register-GEEnforce.ps1
  at the end of setup. Legacy Register-CommonEnforce invocation is
  kept for the transition; it and the legacy per-type enforcer files
  are dead code once Register-GEEnforce runs and will be removed in a
  dedicated cleanup pass.

Standard-Machine manifest:
  eDNC entry bumped 6.4.3 -> 6.4.5. DetectionValue pinned to the
  4-part FileVersion 6.4.5.0 verified against a fresh install in the
  Win11 analyzer VM. UDC DetectionValue pinned to 1.0.34 (registry
  stores 3-part for UDC; verified live).

scripts/mirror-from-gold.sh:
  Restructured around share-root rsyncs (one pass per Samba share)
  to close gaps in the prior per-subdir layout: winpeapps/_shared/
  Applications (7.5 GB of Adobe + fonts + Java + Office + OpenText
  + printdrivers + wireless + Zscaler), additional winpeapps image
  types, and enrollment flat-layout root files. Adds
  --skip-clonezilla and --skip-reports.

Verified end-to-end in the Win11 analyzer VM:
  - Every action Type and DetectionMethod round-tripped
  - PCTypes filter (Oracle excluded on Shopfloor, Firefox included
    on Shopfloor and DESKTOP-*, excluded elsewhere)
  - TargetHostnames filter (exact, wildcard, no-match)
  - Upgrade path: XML hash bump + fleet re-copy
  - Rollback path: history-archive restore propagates via enforcer,
    fleet converges back without per-PC intervention
  - Status writeback + monitor script drift detection
  - Graceful degradation on bad creds, missing creds, share
    unreachable (all exit 0, log clearly, retry next cycle)

Not in this commit (follow-ups):
  - Retire legacy per-type *-Enforce.ps1 files and simplify
    09-Setup-*.ps1 scripts (coordinated multi-file cleanup)
  - Stage 2b: InUseCheck close-and-reopen, ApplyMode gating,
    UpdateWindow, .apply-now.txt sentinel, BITS pre-staging,
    1618 mutex retry, PostInstallCheck, Uninstall action
  - Management app (manifest CRUD + deploy + rollback + fleet view)
  - ShopFloor autologon persistence bug (deferred for next imaging
    attempt with live registry evidence)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:19:23 -04:00
cproudlock
6dcf832ace Keyence ongoing-update enforcer (tsgwp00525 share pattern)
Adds a CMM-style logon enforcer so VR-6000 updates push fleet-wide
without re-imaging.

- keyence-manifest.json: declares VR-6000 MSI (ProductCode-keyed) and
  KEYENCE VR USB driver (pnputil-keyed). Single source of truth for
  both imaging-time and ongoing-enforcement paths.
- lib/Install-FromManifest.ps1: forked from CMM/lib; adds DetectionMethod
  "pnputil" (regex-matches `pnputil /enum-drivers` output) and Type
  "INF" (invokes `pnputil /add-driver /install`). Everything else
  unchanged so CMM-style error parsing + MSI log scanning carry over.
- Keyence-Enforce.ps1: forked from CMM-Enforce.ps1. SYSTEM scheduled
  task, logon trigger, mounts tsgwp00525 SFLD share with creds from
  HKLM:\SOFTWARE\GE\SFLD\Credentials (provisioned by Azure DSC),
  hands off to Install-FromManifest against the share manifest.
- 09-Setup-Keyence.ps1: rewritten around the manifest. Runs
  Install-FromManifest at imaging time, stages runtime scripts to
  C:\Program Files\GE\Keyence, registers "GE Keyence Enforce"
  scheduled task. Idempotent.
- site-config.json: add keyenceSharePath to the Keyence profile
  pointing at \\tsgwp00525\shared\dt\shopfloor\keyence\machineapps.

To push a new VR-6000 version: drop the new MSI + updated manifest on
the tsgwp00525 share, every Keyence PC upgrades on next logon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 10:16:20 -04:00
cproudlock
22c59b889e Keyence VR-6000 Series Software + USB driver deployment
- shopfloor-setup/Keyence/09-Setup-Keyence.ps1: populate placeholder with
  MSI install via msiexec and driver install via pnputil. Idempotent on
  ProductCode {058E7194-...} and DriverStore entry. Logs to C:\Logs\Keyence\.
- shopfloor-setup/Keyence/installers/VR-6000 Series Software.msi: main
  product (1.7 MB; pulled from Keyence6000.exe Inno wrapper's Windows
  Installer cache, built with InstallShield 2019).
- shopfloor-setup/Keyence/drivers/: KEYENCE VR Series USB driver
  package (.inf + .cat + Wdf/WinUsb co-installers). 2.7 MB, pulled from
  DriverStore\FileRepository\keyence_vr_series.inf_amd64_b5e5eb0924d7b4ce.
- preinstall.json: add VC++ 2013 x64 Min + Add entries (PCTypes: ["*"])
  as prereqs for VR-6000. GUIDs {A749D8E6-B613-...} and {929FBD26-9020-...}.

Staging footprint for non-Keyence PCs is unchanged (the 4.4 MB Keyence
payload lives under shopfloor-setup/Keyence/ which startnet.cmd only
xcopies for PCTYPE=Keyence). Rollout still requires dropping the two
VC++ 2013 x64 MSIs into \$PXE_IMAGES_DIR/dependencies/vcredist/2013-x64-{min,add}/
on the workstation running sync-preinstall.sh.

Rationale for bundling the MSI + driver locally rather than running
Keyence6000.exe: the Inno wrapper calls an InstallShield child (Setup.exe)
without silent flags, which hangs indefinitely in session 0 during
automated imaging. msiexec + pnputil from the extracted bundle runs
fully non-interactive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 10:02:34 -04:00
cproudlock
719a550be8 Add defensive .gitignore patterns for secrets/keys
Blocks .env, *.key, *.pem, id_rsa*, secrets.*, credentials.json, etc.
Preventive — no exposure found; future commits can't accidentally
leak these.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:50:12 -04:00
cproudlock
eca9ee2b36 startnet.cmd: stage udc-backups to WinPE; mirror-from-gold: taxonomy layout
- playbook/startnet.cmd + startnet-template.cmd: after preinstall staging,
  xcopy Y:\pre-install\udc-backups to W:\PreInstall\udc-backups so UDC
  settings JSONs are available during image deployment. Harvested from
  live gold where this block existed but was never committed.

- scripts/mirror-from-gold.sh: update source paths to current taxonomy
  layout (pre-install/, installers-post/, blancco/, config/) and add
  ppkgs/, scripts/, shopfloor-setup/ sections. Added --delete for exact
  mirror semantics. Used to seed the spare PXE server at 10.9.100.2 on
  2026-04-16 from gold at 10.9.100.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:37:23 -04:00
cproudlock
6e85e19c85 S: drive mapping via HKLM\Run, autologon-count non-intervention, Phase 4 no-scripts handling
- Register-MapSfldShare.ps1: swap scheduled task for HKLM\Run entry. Task with -GroupId runs in session 0 with no HKCU, so /persistent:yes fails and the drive mapping isn't visible to Explorer. Run key fires at Explorer startup in the interactive user's session with full token + HKCU. Unregisters legacy 'GE Shopfloor Map S: Drive' task for PCs already imaged.
- Run-ShopfloorSetup.ps1: stop bumping AutoLogonCount (99 at start, 4 at end). Windows decrements per-logon and at 0 clears AutoAdminLogon + DefaultPassword, which nukes the lockdown-configured ShopFloor autologon. Re-enable-wired-NICs task now gates on Autologon_Remediation.log 'Autologon set for ShopFloor' instead of SFLD creds, so wired stays off through the whole Intune+DSC+lockdown chain.
- Monitor-IntuneProgress.ps1: Phase 4 treats 'no custom scripts' as COMPLETE when DSC install is done (was WAITING, which stalled the state machine on PC types without scripts). Push retrigger out to 15min when entering lockdown-wait so a stale 5min retrigger doesn't fire mid-Remediation. Removed the AutoLogonCount delete in Invoke-SetupComplete since we no longer set it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:42:22 -04:00
cproudlock
2ab6055125 Fix ShopFloor autologon persistence, S: drive mapping, sync throttle
AutoLogonCount depletion:
  Run-ShopfloorSetup set AutoLogonCount=4 for SupportUser. Windows
  decrements per-logon; at 0 it clears AutoAdminLogon + DefaultPassword,
  nuking the lockdown-configured ShopFloor autologon. Fix: delete
  AutoLogonCount in Invoke-SetupComplete before the lockdown reboot.
  ShopFloor's Autologon.exe-set config persists indefinitely.

Sync_intune window on ShopFloor:
  The marker-check path used 'exit 0' but the task runs with -NoExit,
  leaving a dangling PowerShell window on every ShopFloor logon. Fix:
  [Environment]::Exit(0) kills the host outright, defeating -NoExit.

S: drive mapping:
  Vendor ConsumeCredentials.ps1 calls New-StoredCredential -Persist
  LocalMachine (needs admin) before net use. ShopFloor is non-admin so
  cred-store fails silently and net use has no auth. Fix: new
  Map-SfldShare.ps1 reads HKLM creds and passes them inline to
  net use /user: -- no Credential Manager needed, works as Limited.
  Register-MapSfldShare updated to stage + reference our script.

Wired NIC re-enable:
  SYSTEM task polls for SFLD creds (Phase 5), re-enables wired NICs,
  self-deletes. Replaces the broken Enable-NetAdapter in Monitor
  (Limited principal can't enable NICs). No-WiFi devices unaffected
  (migrate-to-wifi never disables, re-enable is a no-op).

Sync throttle:
  15 min retrigger when only waiting for lockdown (was 5 min for all
  phases). Avoids interrupting the Intune Remediation script.

Defect Tracker path:
  All references corrected to C:\Program Files (x86)\WJF_Defect_Tracker.

QR code retry:
  Build-QRCodeText retried every poll cycle until DeviceId appears
  (was single-shot that could miss the dsregcmd timing window).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:29:02 -04:00
cproudlock
f73f999938 Unified Common-Enforce for cross-type apps, add WJF Defect Tracker
Replaces the Acrobat-only enforcer with a generic Common-Enforce that
handles all cross-PC-type apps from one manifest + one scheduled task
on the SFLD share at \\tsgwp00525\shared\dt\shopfloor\common\apps\.

Renames:
  Acrobat-Enforce.ps1        -> Common-Enforce.ps1
  Register-AcrobatEnforce    -> Register-CommonEnforce
  acrobat-manifest.json      -> common-apps-manifest.json
  common.acrobatSharePath    -> common.commonAppsSharePath
  'GE Acrobat Enforce' task  -> 'GE Common Apps Enforce' task
  C:\Program Files\GE\Acrobat -> C:\Program Files\GE\CommonApps

Register-CommonEnforce cleans up the legacy 'GE Acrobat Enforce' task
if present from a prior image.

WJF Defect Tracker (replaces ClickOnce):
  - Added to preinstall.json (PCTypes=*, fleet-wide imaging-time install)
  - MSI staged on PXE at pre-install/installers/
  - Added to common-apps-manifest with FileVersion detection on
    C:\Program Files\WJF_Defect_Tracker\Defect_Tracker.exe
  - site-config + 06-OrganizeDesktop: shortcut changed from ClickOnce
    'existing' to exe-path pointing at the MSI-installed binary
  - Update workflow: drop new MSI on share, bump DetectionValue

CMM 09-Setup-CMM: added goCMM + DODA to the ACL grant list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:13:05 -04:00
cproudlock
1a5feefb01 CMM: grant Users Modify on PC-DMIS install dirs for non-admin launch
PC-DMIS writes settings, probe configs, and measurement data to its own
Program Files install directory at runtime. Without Modify permission
for BUILTIN\Users, non-admin accounts (ShopFloor) get a UAC elevation
prompt on every launch. The "run as admin once" workaround can't be
automated because PC-DMIS shows a license dialog on first run that
blocks silently.

Fix: grant BUILTIN\Users Modify with inheritance on:
  - C:\Program Files\Hexagon\PC-DMIS 2016.0 64-bit
  - C:\Program Files\Hexagon\PC-DMIS 2019 R2 64-bit
  - C:\ProgramData\Hexagon

Runs as Step 2.5 in 09-Setup-CMM.ps1 after Install-FromManifest
completes. If the exe also has an embedded requireAdministrator manifest
(separate from the file-permission issue), that will need an additional
fix after testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:38:24 -04:00
cproudlock
ac23759486 UDC firewall rules + Acrobat Reader as default PDF viewer
- Pre-create Windows Firewall inbound-allow rules for UDC.exe and
  MTConnect agent.exe before UDC_Setup.exe runs, suppressing the
  interactive "allow through firewall?" dialogs during silent install.

- Set Adobe Acrobat Reader (Acrobat.Document.DC) as the default .pdf
  handler via dism /import-defaultappassociations. Runs in
  03-ShellDefaults.ps1 so the OEMDefaultAssociations.xml is in place
  before ShopFloor's profile is created on first logon. Edge no longer
  claims .pdf on new profiles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:18:44 -04:00
cproudlock
85e74e5dd1 UDC settings: pre-stage from server backups, fix arg format, action prompts
Root cause found via decompiling UDC_Setup.exe: it never writes
udc_settings.json from CLI args. Instead it pulls
Settings_Backups\udc_settings_<num>.json from \\tsgwp00525\shared\SPC\UDC
-- which is unreachable at imaging time (no SFLD creds yet). Silent
File.Exists() false, settings never copy, UDC lands on Evendale defaults.

Fix: stage 80 udc_settings_*.json backups under
shopfloor-setup/Standard/udc-backups/ (same tree as ntlars-backups,
xcopy'd to C:\Enrollment\ by existing startnet.cmd). 00-PreInstall
pre-creates C:\ProgramData\UDC\udc_settings.json from the matching
backup BEFORE UDC_Setup.exe runs. Installer's server-side copy silently
fails (unreachable), our pre-staged file survives.

Also:
- preinstall.json UDC InstallArgs corrected: "West Jefferson" -9999
  (quoted spaced site + dash-prefixed number, confirmed via decompile)
- Update-MachineNumber.ps1 UDC.exe relaunch: quoted site + dash number
- Monitor-IntuneProgress: action prompts (Select Device Category after
  Phase 1; Initiate ARTS Lockdown after Phase 5/creds), Display flow
  (3-phase: Registration -> Config -> Lockdown), Phase 6 IME-based
  lockdown detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:44:34 -04:00
cproudlock
db55bd772a sync_intune: professional UI, IME-based lockdown detection
UI overhaul:
  Replaced the 30+ line checkbox-per-sub-item view with a clean
  6-line phase summary styled for GE Aerospace branding. Each phase
  shows one colored status tag: [COMPLETE] green, [IN PROGRESS] cyan,
  [WAITING] gray, [FAILED] red. Action hint for Phase 2 (device
  category assignment) in yellow. QR code + Device ID below.

Phase 6 lockdown detection:
  Replaced DefaultUserName + admin-rename checks (which pass at PPKG
  time, way too early) with Intune Remediation log artifacts:
  - Autologon_Remediation.log: "Autologon set for ShopFloor"
  - Autologon_Detection.log: "matches the expected value: 1"
  These only exist after the Intune Remediation cycle actually fires
  post-enrollment, making Phase 6 a true end-of-chain signal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:35:22 -04:00
cproudlock
a4de11814d Force-Lockdown.bat + S: drive logon mapper for ShopFloor end-user
Force-Lockdown.bat (SupportUser desktop):
  Vendor escape hatch when Intune Lockdown push hasn't applied within
  ~30 minutes. Self-elevates via UAC, prompts for typed YES confirmation
  that an ARTS request is in place, then runs sfld_autologon.ps1.

Register-MapSfldShare.ps1 (every PC type):
  The SFLD vendor's 'SFLD - Consume Credentials' scheduled task is
  principal-restricted (admin-only) so it fires for SupportUser logon
  but not for ShopFloor logon -- ShopFloor lands at the desktop with
  no S: drive and no way to reach \\tsgwp00525\shared. Workaround:
  register a parallel 'GE Shopfloor Map S: Drive' AtLogOn task with
  Principal=BUILTIN\Users + RunLevel=Limited that invokes the vendor's
  C:\ProgramData\SFLD\CredentialManager\ConsumeCredentials.ps1 in the
  interactive user's session. Vendor script handles cred-store + net use
  end to end; we just give it a wider trigger principal. Cross-PC-type
  because every shopfloor account needs S:.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:31:18 -04:00
cproudlock
a334a56f1e WiFi detection: widen regex to catch hyphen-less 'WiFi' + 802.11
Realtek RTL8852BE describes itself as 'Realtek RTL8852BE WiFi 6 802.11ax
PCIe Adapter' -- no hyphen in 'WiFi' -- which the previous regex
'Wi-Fi|Wireless' rejected. migrate-to-wifi.ps1's gate then exited 0
silently and neither wired NIC got disabled, leaving the imaging chain
running over PXE ethernet for the entire PPKG phase.

New regex Wi-?Fi|Wireless|WLAN|802\.11 covers:
  - Wi-Fi (Intel-style with hyphen)
  - WiFi (Realtek-style without hyphen)
  - Wireless (Intel Wireless-AC, Killer Wireless)
  - WLAN (some Realtek/MediaTek variants)
  - 802.11 (vendor-agnostic spec reference, fallback)

Applied in two callers:
- migrate-to-wifi.ps1 (3 occurrences: gate + disable + re-enable on timeout)
- Monitor-IntuneProgress.ps1 (re-enable wired on sync_intune startup)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:03:19 -04:00