Commit Graph

17 Commits

Author SHA1 Message Date
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
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
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
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
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
8528a1bcae Install-FromManifest: add FileVersion detection for version-pinned upgrades
File-existence detection on NTLARS.exe couldn't tell eDNC 6.4.3 from 6.4.4
(both installers leave the same binary in place), so the enforcer skipped
upgrades. FileVersion compares the vendor-stamped FileVersion field on a
named binary against the manifest's DetectionValue with exact-string match.

Added to all three lib copies (common, Standard, CMM). Standard manifest
template flipped to FileVersion against DncMain.exe -- the eDNC main
binary is more reliably version-stamped than the bundled NTLARS sub-tool.

Update workflow now: drop the new vendor MSI on the SFLD share, bump
Installer + DetectionValue in machineapps-manifest.json, next user logon
runs Machine-Enforce which detects mismatch and installs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:15:32 -04:00
cproudlock
cc9aad0ea1 Install-FromManifest: add Hash detection for content-versioned files
Needed for eMxInfo.txt (site-specific eDNC config). The file has no
DisplayVersion in the registry and no canonical MSI; we ship it as a
standalone secret on the SFLD share and key drift correction off its
SHA256. When the yearly replacement drops, bump the hash in
machineapps-manifest.json and every Standard-Machine PC catches up on
next logon.

Patched Install-FromManifest in all three copies (CMM, common, Standard)
for consistency. Also adds the eMxInfo.txt entry to the Standard
machineapps-manifest template and an Install-eMxInfo.cmd template that
copies the file into both 32/64-bit eDNC Program Files paths.
2026-04-15 12:37:35 -04:00
cproudlock
8848fca88a Add Acrobat Reader logon enforcer (cross-PC-type), provtool.exe arg fix
Acrobat Reader enforcement:
- playbook/shopfloor-setup/common/ is the cross-PC-type staging dir. Mirrors
  CMM/ structure (enforce script + its Install-FromManifest copy + manifest
  template + register script).
- Acrobat-Enforce.ps1 runs as SYSTEM on every logon, reads
  acrobatSharePath from site-config.common, mounts the SFLD share with
  the same HKLM-backed credential lookup CMM-Enforce uses, hands the
  acrobat-manifest.json from the share to Install-FromManifest.
- Install-FromManifest extended with Type=CMD so it can invoke vendor-
  supplied .cmd wrappers (Install-AcroReader.cmd does a two-step MSI+MSP
  install that does not fit MSI/EXE types cleanly). cmd.exe /c wraps it
  because UseShellExecute=false cannot launch .cmd directly.
- Register-AcrobatEnforce.ps1 stages scripts to C:\Program Files\GE\Acrobat
  and registers "GE Acrobat Enforce" scheduled task. Called from
  Run-ShopfloorSetup.ps1 right before the enrollment (PPKG) step so it
  applies to every PC type, not just CMM.
- acrobat-manifest.template.json is the repo reference; the authoritative
  copy lives on the SFLD share at
  \\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\common\acrobat\
  Bumping Acrobat updates = drop new MSP on share, bump DetectionValue in
  manifest; enforcer catches every PC on next logon.
- site-config.json: add "common": { "acrobatSharePath": ... }. Uses a
  new top-level block rather than a PC-type-specific one since Acrobat
  applies everywhere.

Initial install still happens via the preinstall flow
(Install-AcroReader.cmd during WinPE). The enforcer is the ongoing-
updates side; on a freshly-imaged PC detection passes and it no-ops.

Also in this commit:
- run-enrollment.ps1: provtool.exe argument syntax fix. First test
  returned 0x80004005 E_FAIL in 1s because /ppkg: and /log: are not
  valid provtool flags; the cmdlet's internal call used positional
  path + /quiet + /source. Switched to that syntax.
2026-04-15 09:24:13 -04:00