Commit Graph

170 Commits

Author SHA1 Message Date
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
cproudlock
c23b803dc6 sync_intune: align Phase 3/5/6 columns; ignore benign 'Failed: 0' tails
Cosmetic + accuracy fixes spotted on the live test PC:

- Phase 3 deploy/install lines had a stray double-space after the
  checkbox; Phase 5 'Share creds present in HKLM' and Phase 6
  'Administrator renamed' had wider misalignment. All four lines
  collapsed to single-space-after-checkbox so the column lines up
  with the rest of the table.

- Phase 4 status detector was greping the last 30 lines of each
  Install-*.log for /(?i)\b(ERROR|Failed|exception)\b/. That hit
  benign summary lines like 'Failed: 0' or 'Errors:    0' and
  marked successful runs as failed (Install-VCRedists.ps1 was the
  trigger -- 8/8 'Already installed - skipping' but the summary
  contained 'Failed: 0' and Phase 4 said FAILED). Tightened the
  regex to also exclude /\b(ERROR|Failed|Failures|Errors|Exceptions?)\s*[:=]\s*0\b/
  so the keyword has to be next to a non-zero value (or the
  vocabulary 'Exit code 1603 - FAILED' style still trips correctly).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:53:15 -04:00
cproudlock
2db35c2976 UDC: correct CLI arg signature to compact site + dash-prefixed machine#
UDC_Setup.exe and UDC.exe expect:
  UDC_Setup.exe WestJefferson -7605

Not the spaced-quoted positional pair we'd been passing:
  UDC_Setup.exe "West Jefferson" 7605

The wrong format meant UDC ignored both args, fell back to defaults
(Site=Evendale, MachineNumber=blank). Combined with the kill-after-detect
window, neither value got persisted to udc_settings.json regardless of
whether UDC.exe was given time to write.

Changes:
- preinstall.json: UDC InstallArgs now "WestJefferson -9999"
- 00-PreInstall-MachineApps.ps1: site override now matches/replaces
  the compact 'WestJefferson' token (not 'West Jefferson') and uses
  siteNameCompact from site-config; targetNum extraction regex updated
  to '-(\d+)$' for the new dash-prefix format
- Update-MachineNumber.ps1: UDC.exe relaunch now passes positional
  compact-site + dash-prefixed number instead of -site/-machine flags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:47:57 -04:00
cproudlock
14d103a248 run-enrollment: switch provtool /source from BPRT to PSCmdlet
BPRT was stopping after the first RestartRequired=true command (DotNet35).
Test image captured 2026-04-15 showed 3 of 21 PPKG commands ran (PPKG
Version Check, Lock Screen, DotNet35) before provtool exited 0 leaving
Office / Chrome / Tanium / Activate-Windows / Enable-DeviceLockdown /
Hide-SupportUser / 12 more scripts unexecuted. Symptom: criticalChecks
said EntraID NOT joined (wrong -- it was), sessions.json showed a
'LogonIdleTask' session perpetually 'Not started', and the resulting PC
was missing most of its fleet software.

BPRT is the OOBE runtime source -- it expects the OOBE engine to own the
post-DotNet35 reboot + resume. In our post-autounattend context there is
no OOBE engine, so restart-required commands stall the pipeline. PSCmdlet
is the source Install-ProvisioningPackage uses internally and has the
correct resume semantics for post-OOBE application.

The original motivation for BPRT (avoiding the 180s PowerShell timeout)
does not apply because we invoke provtool.exe directly, not via the
Install-ProvisioningPackage cmdlet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:07:12 -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
a6648c5a40 sync_intune: full lifecycle gate, lockdown phase, creds verification
Add Phase 6 (Lockdown) and tighten Phase 5 so the 5-min Intune sync loop
doesn't declare success until the device is genuinely operator-ready.

- Phase 6 watches two HKLM-level signals confirmed in the 2026-04-15
  pre/post lockdown state diff: Winlogon\DefaultUserName flipped to
  'ShopFloor', and local Administrator renamed to 'SFLDAdmin'. Both land
  via MDM PolicyCSP after DSCInstall.log finishes.

- Phase 5 was just checking that the Consume Credentials scheduled task
  existed; that only proves DSC scheduled it. Now also verifies creds
  actually landed under HKLM:\SOFTWARE\GE\SFLD\Credentials\* with
  TargetHost+Username+Password populated -- which is what Machine/Acrobat/
  CMM-Enforce actually consume.

- Final completion gate: DscInstallComplete && CredsPopulated &&
  LockdownComplete (was just DscInstallComplete). Display PCs unchanged --
  they exit early via the no-DSC Phase 1 path.

- Invoke-SetupComplete now issues shutdown /r /t 10 in AsTask mode after
  writing the sync-complete marker and running the Configure-PC machine#
  prompt. Next boot triggers ShopFloor autologon, which materializes the
  ShopFloor profile from C:\Users\Default (where 03-ShellDefaults already
  baked in TaskbarAl=0, etc.).

- Phase 1->2 gap (waiting for tech to assign device category in Intune
  portal) now shows an explicit ACTION hint instead of empty checkboxes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:01:52 -04:00
cproudlock
6db170bf54 Shell defaults + eDNC reg restore from machine-number backups
- 03-ShellDefaults.ps1: Default-User TaskbarAl=0 (left), HKLM policies to
  hide Start Recommended section, kill Bing web search + suggestions,
  disable Cortana. LTSC-honoured; runs fleet-wide via baseline loop.

- ntlars-backups/: 147 per-machine eDNC registry backups renamed to
  flat <MachineNumber>.reg scheme. Historical off-by-one entries from
  the original dump rewritten to match CSV-target MachineNo.

- Standard/03-RestoreEDncConfig.ps1: at imaging time, if tech typed a
  real machine number at PXE (not 9999), import <num>.reg from the local
  staged copy. Restores eFocas IP, PPDCS serial, Hssb relays -- not just
  the bare MachineNo. Skipped on Timeclock / 9999 / missing backup.

- Update-MachineNumber.ps1: when tech later sets a real number from 9999,
  pull <num>.reg from tsgwp00525 SFLD share (ntlarsBackupSharePath in
  site-config) and reg-import it before writing the new MachineNo.

- Restore-EDncReg.ps1: shared helper (Mount-SFLDShare + Import-EDncRegBackup)
  used by both callers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:42:21 -04:00
cproudlock
67845372b2 Harvest provtool diagnostics, enable ETW channel, skip Timeclock machine#
run-enrollment.ps1:
- Enable Provisioning-Diagnostics-Provider/Admin event log before invoking
  provtool (was disabled by default; no diagnostics survived early runs).
- After provtool returns, copy C:\ProgramData\Microsoft\Provisioning\*
  into C:\Logs\PPKG\ and snapshot HKLM\...\Sessions\* as
  provisioning-sessions.json, plus export the Admin event channel to
  Provisioning-Diagnostics-Admin.evtx. Gives us reviewable state
  without relying on provtool's failure-only diagnostic bundle.
- provtool arg order is positional path + /quiet + /source BPRT (verified
  against ProvEventLog from the PS cmdlet internal call).

startnet.cmd / startnet-template.cmd:
- Standard-Timeclock sub-type skips the machine-number prompt. Timeclock
  PCs do not use a machine number so forcing a prompt wasted tech time
  and left MACHINENUM at the 9999 default anyway. Machine sub-type is
  unaffected.
2026-04-15 14:22:43 -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
3ef981f19e Add Standard-Machine logon enforcer for UDC/eDNC/NTLARS
Reason: Intune DSC's main-category YAML was pushing these to every main
device, including Timeclocks - DSC has no awareness of our pc-subtype
distinction. After UDC/eDNC/NTLARS are removed from the DSC YAML, ongoing
version drift would no longer be corrected. This enforcer replaces that,
scoped correctly by subtype.

Structure mirrors CMM (CMM-Enforce.ps1) and common (Acrobat-Enforce.ps1):
- Machine-Enforce.ps1: SYSTEM logon task; mounts SFLD share with HKLM-
  backed creds; hands off to Install-FromManifest.
- machineapps-manifest.template.json: repo reference; authoritative copy
  lives on the share at \\tsgwp00525.wjs.geaerospace.net\shared\dt\
  shopfloor\main\machineapps\machineapps-manifest.json.
- Register-MachineEnforce.ps1: idempotent setup; stages scripts to
  C:\Program Files\GE\MachineApps and registers the task.
- lib/Install-FromManifest.ps1: copy of the common/ version (already has
  Type=CMD support).

Sub-type gating belt-and-suspenders:
- Run-ShopfloorSetup.ps1 only calls Register-MachineEnforce when
  $pcType -eq "Standard" -and $pcSubType -eq "Machine".
- Machine-Enforce.ps1 itself re-reads pc-subtype.txt and exits early if
  not "Machine", so a mistakenly-deployed copy no-ops.

site-config.json:
- Added "machineappsSharePath" to Standard-Machine pcProfile.

Drive letter U: to stay clear of CMM (S:) and Acrobat (T:) enforcers
that may run concurrently at logon.

Update workflow:
  drop new UDC/eDNC/NTLARS installer on the SFLD share,
  bump DetectionValue in machineapps-manifest.json,
  every Machine PC catches up on next user logon.
2026-04-15 12:16:17 -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
cproudlock
0292bc01ad Auto-flush stale SMB/conntrack state on DHCP lease, one-source PPKG model
Three changes that go together so a re-image never hits "System error 53":

1. dnsmasq dhcp-script hook (playbook/pxe-server-helpers/pxe-dhcp-hook.sh)
   Fires on every add/del lease event. Runs conntrack -D and ss -K for the
   client IP so any stale ESTABLISHED SMB session from a previous boot is
   cleared before the client reconnects. Runs as root (dnsmasq default).
   Wired into /etc/dnsmasq.conf via dhcp-script= directive in the playbook.

2. One-source PPKG (playbook/startnet.cmd + startnet-template.cmd)
   The 5 per-Office PPKG copies were bit-for-bit identical; only the
   filename differs because BPRT parses Office and Region out of the name.
   Store one source file (e.g. GCCH_Prod_SFLD_v4.11.ppkg) and construct
   the BPRT-tagged target filename at menu-selection time from variables:
     SOURCE_PPKG / PPKG_VER / PPKG_EXP / REGION / OFFICE
   copy /Y "Y:\ppkgs\%SOURCE_PPKG%" "W:\Enrollment\%PPKG%"
   Bumped PPKG_VER v4.10 -> v4.11 and PPKG_EXP 20260430 -> 20270430.
   Saves ~30G on disk per version.

3. run-enrollment.ps1 already committed in 5a9c3db uses provtool.exe
   directly (no PowerShell cmdlet 180s timeout). Included here because it
   is part of the same end-to-end PPKG path.
2026-04-15 09:03:16 -04:00
cproudlock
5a9c3db7af run-enrollment.ps1: invoke provtool.exe directly, skip PowerShell cmdlet timeout
Observed today on E8FHGDB4: Install-ProvisioningPackage timed out after
the PowerShell cmdlet's hardcoded 180s limit on a 7.6 GB GCCH v4.10
PPKG. The catch-block fell through to Add-ProvisioningPackage, which
returned "success" but the PPKG diagnostic bundle showed the child
provtool.exe was called with empty packagePathsToAdd (session created,
State=Not started, RebootCount=0). The PC was named, OOBE-completed,
and BPRT apps ran, but the bulk enrollment never applied - PC was not
Entra-joined.

Microsoft Docs GitHub issue 502 confirms the 180s cmdlet timeout is
hardcoded with no configuration option. Quest KB 4376269 suggests
rebuilding the PPKG with the latest Windows Configuration Designer,
but that is upstream and not under our control per PPKG.

Switch to Start-Process -FilePath provtool.exe -Wait. The wait is on
the actual child process, no caller-side timeout. provtool.exe is
what the cmdlet was invoking anyway; we just bypass the wrapper that
imposes the limit.

Sources:
  https://support.quest.com/on-demand-migration/kb/4376269
  https://github.com/MicrosoftDocs/windows-powershell-docs/issues/502
  https://learn.microsoft.com/en-us/windows/configuration/provisioning-packages/provisioning-apply-package
2026-04-15 08:35:35 -04:00
cproudlock
adef507084 Add ARCHITECTURE.md, SITE-CUSTOMIZATION.md, mirror-from-gold.sh
Two new docs cover what the system is (boot chain, services, shares,
enrollment layout, data flow) and what to change per site (every
hardcoded value, where it lives, secrets handling).

scripts/mirror-from-gold.sh replicates content from an existing PXE
server (Operating Systems, drivers, packages, custom installers, BIOS,
PCDMIS, Blancco custom image, site-config) onto a freshly-installed PXE
server. Translates the legacy flat enrollment layout on the source into
the reorganized taxonomy (ppkgs/, pre-install/installers/, installers-
post/cmm/, blancco/, config/) on the destination. Tolerates rsync
exit 23 (permission-denied subdirs like the OpenText W10shortcuts dir
that is pxe-upload-group-only on legacy servers).
2026-04-14 17:56:46 -04:00
cproudlock
d6776f7c7f Reorganize repo, enrollment share taxonomy, Blancco USB-build fixes, v4.10 PPKGs
Workstation reorganization:
- All build/deploy/helper scripts moved into scripts/ (paths updated to use
  REPO_ROOT instead of SCRIPT_DIR so they resolve sibling dirs from the new
  depth)
- New config/ directory placeholder for site-specific overrides
- Removed stale: mok-keys/, test-vm.sh, test-lab.sh, setup-guide-original.txt,
  unattend/ (duplicate of moved playbook/FlatUnattendW10.xml)
- README.md and SETUP.md structure listings updated, dead "Testing with KVM"
  section removed
- .claude/ gitignored

Enrollment share internal taxonomy (forward-looking; existing servers
unaffected since they keep their current boot.wim with flat paths):
- Single SMB share kept (WinPE only mounts one Y: drive), but content now
  organised into ppkgs/, scripts/, config/, shopfloor-setup/, pre-install/{bios,
  installers}, installers-post/cmm/, blancco/, logs/
- README.md deployed to share root explaining each subdir
- New playbook tasks deploy site-config.json + wait-for-internet.ps1 +
  migrate-to-wifi.ps1 explicitly (were ad-hoc on legacy servers)
- BIOS subdir moved into pre-install/bios/, preinstall/ renamed to pre-install/
- startnet.cmd + startnet-template.cmd updated with new Y:\subdir\ paths
- Bumped GCCH PPKG references v4.9 -> v4.10

Blancco USB-build fixes (so next fresh USB install boots Blancco end-to-end
without the manual fixup we did against GOLD):
- grub-blancco.cfg: kernel/initrd switched HTTP -> TFTP (GRUB's HTTP module
  times out on multi-MB files); added modprobe.blacklist=iwlwifi,iwlmvm,btusb
  (WiFi drivers hang udev on Intel business PCs)
- grubx64.efi rebuilt from updated cfg
- Playbook task added to create /srv/tftp/blancco/ symlinks pointing at the
  HTTP-served binaries

run-enrollment.ps1: OOBEComplete is now set AFTER PPKG install (Win11 22H2+
hangs indefinitely if OOBEComplete is set before the bulk-enrollment PPKG runs).

Also includes deploy-bios.sh / pull-bios.sh / busybox-static / models.txt
that were sitting untracked at the repo root.
2026-04-14 16:01:02 -04:00
cproudlock
d14c240b48 Change dnsmasq-restart cron delay from 30s to 15s
Task name already said "15s after reboot" but content had sleep 30.
Align content with name; faster recovery from systemd-resolved race at boot.
2026-04-14 13:01:38 -04:00
cproudlock
ade2f3b5ff Fix USB install reliability: bash, LV resize, deps, idempotency
- autoinstall/user-data: move lvextend/growpart/pvresize BEFORE playbook
  so 130GB of drivers+PPKGs fits during first-boot copy. Use
  tr -d "[:space:]" to avoid breaking outer bash -c single-quote wrap.
- playbook: add executable: /bin/bash to Dell driver deploy (process
  substitution) and Blancco initramfs builder (brace expansion).
- playbook: make "Ensure Samba user for Blancco reports" idempotent via
  pdbedit check so re-runs don't abort the play.
- download-packages.sh: also download dist-upgrade package set. Explicit
  --simulate misses transitive version bumps (e.g. gnupg 17.4 needs
  matching gpgv 17.4) causing offline dpkg "dependency problems" when
  ISO baseline is older than noble-updates.
2026-04-14 12:57:28 -04:00
cproudlock
855af7312b Sub-type aware preinstall, USB drivers/PPKGs, Lab OpenText
- PreInstall runner reads pc-subtype.txt and matches PCTypes against
  both base type (Standard) and composite key (Standard-Machine).
- UDC scoped to Standard-Machine only. eDNC and MachineNumberACLs
  skip on Standard-Timeclock sub-type.
- Lab added to OpenText PCTypes.
- build-usb.sh copies enrollment/ (PPKGs) and drivers-staging/ (Dell
  driver packs) onto USB for self-contained deployment.
- Playbook deploys PPKGs and drivers from USB to PXE server shares.
- Gitignore enrollment/, drivers-staging/, *.ppkg (large binaries).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:00:23 -04:00
cproudlock
855d501fc2 Fix Display sync loop, PPKG deployment, dnsmasq cron, dpkg configure
- Monitor-IntuneProgress: Display PCs skip DSC phases entirely (no SAS
  token, no DSCInstall.log), complete after Phase 1 identity. Renderer
  hides Phase 2-5 for Display type.
- Playbook: deploy PPKG files and run-enrollment.ps1 from USB to
  enrollment share. Bump dnsmasq restart cron from 15s to 30s.
- build-usb.sh: copy enrollment/ directory (PPKGs) onto USB if present.
- user-data: add dpkg --configure -a after offline .deb install to fix
  packages left in unconfigured state (cron, systemd-timesyncd).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:27:21 -04:00
cproudlock
743bc91996 Shopfloor Display: move kiosk app install to preinstall system
Install-KioskApp.cmd wrapper reads display-type.txt and runs the
matching Inno Setup installer (Lobby or Dashboard). Replaces the
standalone 09-Setup-Display.ps1 for uniform app install pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:38:59 -04:00
cproudlock
8455c80aa4 Shopfloor preinstall: add Adobe Acrobat Reader DC for all PC types
Same version as GEA-Engineering image. Two-step install via wrapper
script (MSI + enterprise transform, then DC update patch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:51:21 -04:00
cproudlock
f3211dfd29 CMM test iteration: desktop shortcuts, rename to 09-Setup-*, defer NIC re-enable
Rolls up everything from the CMM imaging test iteration tonight. No
single concern - several small, related polish items on the option-3
patched-MSI pipeline and the shopfloor-setup / sync_intune handoff.

- Rename all type-specific "01-Setup-<Type>.ps1" scripts to
  "09-Setup-<Type>.ps1" across CMM, Display, Genspect, Keyence, Lab,
  and WaxAndTrace. The "01-" prefix implied the script runs first in
  the overall sequence when it actually runs between baseline (00, 04)
  and finalization (06, 07). Logs now read "Running CMM setup:
  09-Setup-CMM.ps1" which matches the real position. Standard/
  01-eDNC.ps1 + 02-MachineNumberACLs.ps1 left alone - those digits
  represent real within-type ordering.
- playbook/shopfloor-setup/site-config.json CMM profile updates:
  - startupItems = [] (empty). Previously had WJ Shopfloor auto-launch
    which the user does not want on CMM workstations. Now relies on
    the Get-ProfileValue empty-array fix to not fall through to site
    defaults.
  - desktopApps + taskbarPins gain entries for PC-DMIS 2016, PC-DMIS
    2019 R2, CLM Admin, and goCMM so 06-OrganizeDesktop Phase 2
    materializes them into C:\\Users\\Public\\Desktop\\Shopfloor Tools\\
    and 07-TaskbarLayout pins them. goCMM is under C:\\Program Files
    (x86)\\General Electric\\goCMM\\ (GE product, not Hexagon).
- playbook/shopfloor-setup/Run-ShopfloorSetup.ps1: remove the blocking
  "UNPLUG ethernet cable, press any key" prompt + the interactive
  wired-NIC re-enable. The whole prompt block was a hard blocker on
  the imaging chain that required a human to walk to each PC.
- playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1:
  re-enable wired NICs unconditionally at the top of the transcript.
  This is the new home for the re-enable that used to live behind the
  prompt in Run-ShopfloorSetup. By the time sync_intune fires (after
  PPKG reboot + auto-login + Stage-Dispatcher), the tech has had
  minutes of wall-clock time to physically rewire from PXE to
  production without us blocking on a keypress. Tower case is a
  no-op because migrate-to-wifi.ps1 already left wired enabled.
- Internal comment updates in 09-Setup-CMM.ps1, cmm-manifest.json,
  Install-FromManifest.ps1, and startnet.cmd (+ startnet-template)
  to reflect the new filename.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:03:09 -04:00
cproudlock
b88e4d3272 CMM: patched-MSI install chain for PC-DMIS 2016 + 2019 R2
PC-DMIS refuses to install without a valid license in /qn mode; its
BA / MSI custom actions ProcessLicensingFromBundle (which spins for
~13 minutes trying to activate against licensing.wilcoxassoc.com)
and IsLicenseDateValid (which errors 1603 when no license file is
present) are the gate. Bypassed by dark-extracting the chained MSIs
from each Burn bundle and pre-patching both custom actions'
InstallExecuteSequence.Condition columns to '0' via Windows
Installer COM SQL UPDATE. The patched MSIs install cleanly with no
license, PCDLRN.exe loads at runtime, hits its own runtime license
check, and shows the normal "no license" dialog. Tech activates via
clmadmin.exe post-imaging and PC-DMIS launches normally.

- playbook/preinstall/preinstall.json: adds VC++ 2010 x64 and VC++
  2012 x64 redistributable entries scoped to all PC types. PC-DMIS
  links msvcr100.dll / mfc100u.dll (VS 2010) and msvcr110.dll /
  mfc110u.dll (VS 2012); without these the exe gets DLL_NOT_FOUND
  (0xC0000135) at launch. Win11 ships VC++ 2022 (covers 2015+) but
  not 2010/2012, so we ship these from the dark-extracted bundle
  payloads. Small (~13 MB combined), inert on PCs that don't need
  them, so the filter is "*".
- playbook/shopfloor-setup/CMM/cmm-manifest.json: version 2.0.
  Drops the bundle EXEs, installs patched MSIs directly with
  properly quoted INSTALLFOLDER / APPLICATIONFOLDER paths (the
  earlier "hangs" were caused by Start-Process splitting unquoted
  paths on spaces, not actual msiexec hangs). Skips the chained
  CLM Tools 1.5/1.7 MSIs - CLM 1.8.73 standalone provides the same
  interfaces and PC-DMIS MSIs have no LaunchCondition requiring
  Tools 1.5 / 1.7 specifically. Keeps Protect Viewer from the 2019
  R2 bundle as a separate entry. CLM 1.8 and goCMM bundles run
  unpatched (no install-time license check).
- playbook/sync-cmm.sh: now also includes *.msi files in the
  upload set, not just *.exe.

Known caveats: patched MSIs have HashMismatch signatures (expected
- Windows Installer accepts them in /qn mode on locally-cached
  machines). Every Hexagon bundle version bump requires re-dark-
extracting and re-patching. Unsupported by Hexagon; do not call
them for install-related issues without reverting to the original
bundles first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 16:52:54 -04:00
cproudlock
c595d3b9cb Shopfloor unattend: move Orders 4/5 logic to external PS1 scripts
FlatUnattendW10-shopfloor.xml was rejected by Windows OOBE with
"the answer file is invalid" after the earlier tower-no-WiFi fix.
Root cause: the inline PowerShell in <CommandLine> for Orders 4 and
5 exceeded the SynchronousCommand CommandLine length limit (~1024
chars) and/or contained characters the unattend schema validator
dislikes.

Fix: move the logic to two external PS1 scripts and shrink both
CommandLine entries to ~85 chars each that just invoke the scripts.

- playbook/wait-for-internet.ps1: 60s interactive prompt ("connect
  production network now"), then poll TCP 443 to login.microsoft-
  online.us for up to 10 min with a hard timeout so the loop always
  exits. Uses Test-NetConnection -Port 443 (not Test-Connection /
  ICMP) because Microsoft 365 edges do not reliably respond to ping.
- playbook/migrate-to-wifi.ps1: Gates the entire wired-disable
  migration on "does a WiFi adapter exist?" If not (tower), the
  script is a no-op. If yes, disable wired / wait for WiFi internet
  with a 5 min timeout / re-enable wired on timeout fallback.
- startnet.cmd stages both new scripts to W:\Enrollment\ next to
  run-enrollment.ps1 during the WinPE phase.
- FlatUnattendW10-shopfloor.xml Orders 4 and 5 shrunk to short
  invocations of C:\Enrollment\wait-for-internet.ps1 and
  C:\Enrollment\migrate-to-wifi.ps1.
- startnet-template.cmd kept in sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 16:50:58 -04:00
cproudlock
3ea20b271e Shopfloor unattend: fix tower (no-WiFi) hang on internet wait
FlatUnattendW10-shopfloor.xml Orders 4 and 5 hung forever on
desktops/towers with no WiFi NIC. Two underlying bugs:

1. Order 4 used Test-Connection (ICMP) against login.microsoftonline.us.
   Microsoft 365 endpoints do not reliably respond to ICMP, so even
   with working TCP 443 internet the ping loop ran forever. Symptom
   on a user-facing machine was the PowerShell window permanently
   stuck on "Waiting for internet connectivity...".
2. Order 5 unconditionally disabled all wired adapters and waited for
   WiFi internet. On a tower with no WiFi NIC this left the machine
   completely offline, and the following while loop waited for a WiFi
   connection that could never happen.

Fixes:
- Order 4 now emits a 60s interactive prompt asking the user to
  connect to the production network (so towers have a window to
  unplug PXE and plug into a production port), then uses
  Test-NetConnection -Port 443 with a 10 min hard timeout so the
  loop always exits.
- Order 5 checks for a physical WiFi adapter first; if none exists
  (tower case), it logs "No WiFi adapter - staying on ethernet" and
  returns immediately instead of disabling wired. If WiFi is present
  and migration times out, wired adapters are re-enabled as a
  fallback so the machine is never left offline.

Both orders now use Test-NetConnection -Port 443 instead of
Test-Connection (ICMP) so ICMP-blocking firewalls and non-responsive
cloud endpoints no longer produce infinite waits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:25:41 -04:00
cproudlock
18537acbbc PXE server: fix WinPE re-image SMB connection loss
WinPE clients re-imaging the same machine hit "System error 53 -
network path not found" on the second attempt. systemctl restart smbd
did not help; only a full server power cycle cleared the state.

Root cause is kernel nf_conntrack: the default TCP ESTABLISHED timeout
is 5 days (432000s), so a session from the first WinPE run whose
client rebooted abnormally leaves an ASSURED ESTABLISHED entry that
ufw's state-tracking rules then mis-classify the new SYN against.

Fix applied in three layers:
- /etc/sysctl.d/99-pxe-conntrack.conf drops TCP ESTABLISHED timeout
  to 1 hour and shortens the half-closed states to 30s each.
- smb.conf gains socket options TCP_NODELAY SO_KEEPALIVE IPTOS_LOWDELAY
  plus keepalive = 30 and deadtime = 5. Active sessions refresh the
  conntrack timer every 30s via keepalives so they never age out;
  dead ones expire in an hour.
- /usr/local/sbin/smb-diag.sh snapshots kernel + Samba state for
  remote diagnosis; /usr/local/sbin/smb-soft-reset.sh walks a
  progressive recovery (nmbd/smbd restart, conntrack flush, arp
  flush, ss -K) as an alternative to power-cycling.

conntrack package added to download-packages.sh and playbook verify
list so the offline .deb bundle ships with it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 13:00:43 -04:00
cproudlock
ee7d3bad66 Shopfloor imaging: CMM type, Configure-PC override fix, serial drivers
- CMM imaging pipeline: WinPE-staged bootstrap + on-logon enforcer
  against tsgwp00525 share, manifest-driven installer runner shared via
  Install-FromManifest.ps1. Installs PC-DMIS 2016/2019 R2, CLM 1.8,
  goCMM; enables .NET 3.5 prereq; registers GE CMM Enforce logon task
  for ongoing version enforcement.
- Shopfloor serial drivers: StarTech PCIe serial + Prolific PL2303
  USB-to-serial via Install-Drivers.cmd wrapper calling pnputil
  /add-driver /subdirs /install. Scoped to Standard PCs.
- OpenText extended to CMM/Keyence/Genspect/WaxAndTrace via
  preinstall.json PCTypes; Defect Tracker added to CMM profile
  desktopApps + taskbarPins.
- Configure-PC startup-item toggle now persists across the logon
  sweep via C:\\ProgramData\\GE\\Shopfloor\\startup-overrides.json;
  06-OrganizeDesktop Phase 3 respects suppressed items.
- Get-ProfileValue helper added to Shopfloor/lib/Get-PCProfile.ps1;
  distinguishes explicit empty array from missing key (fixes Lab
  getting Plant Apps in startup because empty array was falsy).
- 06-OrganizeDesktop gains transcript logging at C:\\Logs\\SFLD\\
  06-OrganizeDesktop.log and now deletes the stale Shopfloor Intune
  Sync task when C:\\Enrollment\\sync-complete.txt is present (task
  was registered with Limited principal and couldn't self-unregister).
- startnet.cmd CMM xcopy block (gated on pc-type=CMM) stages the
  bundle to W:\\CMM-Install during WinPE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:58:47 -04:00
cproudlock
bc123c1066 Machine number input at PXE menu for Standard PCs
Adds a machine number prompt to startnet.cmd after the Standard sub-type
selection. Tech enters the number during the PXE boot process. Defaults
to 9999 if Enter is pressed (existing placeholder behavior).

Written to C:\Enrollment\machine-number.txt alongside pc-type.txt.

Consumers:
  00-PreInstall-MachineApps.ps1 - replaces 9999 in UDC InstallArgs with
    the entered number, so UDC installs with the correct machine number
    from the start (no post-setup Set-MachineNumber needed).
  01-eDNC.ps1 - writes the machine number to the DNC\General\MachineNo
    registry value during eDNC install.
  Configure-PC.ps1 - existing $needsMachineNumber check already skips
    the prompt when UDC/eDNC aren't at 9999, so no change needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 08:50:02 -04:00
cproudlock
3d5814cd7c Use marker file instead of task unregister for sync completion
BUILTIN\Users (Limited RunLevel) can't delete scheduled tasks, so
Unregister-ScheduledTask failed silently and the sync task kept firing
at every logon even after completion.

Fix: write C:\Enrollment\sync-complete.txt on completion. At script
startup in -AsTask mode, check for the marker and exit immediately if
found. The task stays in Task Scheduler but does nothing -- fires at
logon, sees marker, exits in under a second. No visible window.

Manual sync_intune.bat runs (no -AsTask) ignore the marker and always
show the full status display for inventory QR code purposes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:09:11 -04:00
cproudlock
6d887346b6 Add Chrome homepage + startup tabs mirroring Edge config
Chrome (installed by PPKG) now gets the same profile-driven homepage
and startup tabs as Edge. Uses HKLM:\SOFTWARE\Policies\Google\Chrome
with the same policy keys (RestoreOnStartup, RestoreOnStartupURLs,
HomepageLocation, HomepageIsNewTabPage, ShowHomeButton).

Reuses the $startupTabs and $homepageUrl already resolved for Edge
from the PC profile, so both browsers show identical tabs. Skips
cleanly if Chrome isn't installed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:56:48 -04:00
cproudlock
07ebe819bd Auto-apply startup items from profile, Configure-PC -MachineNumberOnly
Three changes to eliminate the redundant startup-item picker during
the imaging chain:

06-OrganizeDesktop.ps1 - new Phase 3: auto-apply startup items
  Reads pcProfile.startupItems (or site-wide default) and creates
  .lnk files in AllUsers Startup folder. Supports exe, existing, and
  url types (same as Configure-PC). Idempotent - skips items that
  already exist so manual changes aren't overwritten. Runs during
  shopfloor setup finalization, so the tech doesn't need to select
  startup items again.

Configure-PC.ps1 - new -MachineNumberOnly switch
  When set, skips the entire startup-items section and only shows the
  machine number prompt (if UDC/eDNC at 9999). Used by sync_intune
  -AsTask after completion. Full startup picker still available when
  the tech opens Configure-PC.bat manually from the desktop.

Monitor-IntuneProgress.ps1 - simplified -AsTask completion
  After post-reboot DSC complete: unregisters task, launches
  Configure-PC -MachineNumberOnly, exits. Tech uses sync_intune.bat
  on the desktop to see QR code for inventory purposes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:54:31 -04:00
cproudlock
15f67063bd Refresh QR code after AAD join detected
The QR code text was built once at script startup. If the device wasn't
AAD-joined yet, it showed "Device not yet Azure AD joined" forever -
even after Phase 1 checks passed. Now regenerates Build-QRCodeText
when Phase1.AzureAdJoined transitions to true.

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