Commit Graph

52 Commits

Author SHA1 Message Date
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
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
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
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
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
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
cproudlock
b69d68f7b5 Register sync task BEFORE enrollment (PPKG reboot kills run-enrollment)
Install-ProvisioningPackage triggers an immediate reboot that kills
run-enrollment.ps1 before it can register the sync_intune task or do
any post-install work. BPRT app installs happen on the NEXT boot, not
before the reboot.

Fix: move sync task registration into Run-ShopfloorSetup.ps1, executed
BEFORE calling run-enrollment.ps1. The task is safely registered while
we still have control. Then enrollment installs the PPKG and lets it
reboot. After reboot, BPRT finishes in background, sync task fires at
logon, monitors Intune enrollment (which is independent of BPRT).

Run-ShopfloorSetup.ps1:
  - Registers "Shopfloor Intune Sync" @logon task after desktop tool
    copies but BEFORE enrollment
  - Flushes transcript before calling enrollment (since PPKG reboot
    will kill us, ensures log is complete)
  - Enrollment is the absolute last call

run-enrollment.ps1:
  - Stripped to essentials: find PPKG, rename computer, set OOBE,
    Install-ProvisioningPackage
  - No BPRT polling (irrelevant - happens after reboot)
  - No task registration (already done by caller)
  - No shutdown call (PPKG handles it)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:15:45 -04:00
cproudlock
db3f126fb3 Bump AutoLogonCount from 2 to 4 for reboot margin
The imaging chain needs exactly 2 autologons after the first boot
(sync_intune pre-reboot + post-reboot). Setting to 4 adds 2 extra
reboots of margin for unexpected restarts from Windows Update, PPKG
file operations, or script crashes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:41:23 -04:00
cproudlock
c06310f5bd Replace all Unicode characters with ASCII in playbook scripts
Em dashes (U+2014) and arrows (U+2192) break PowerShell 5.1 on
Windows when the file has no UTF-8 BOM -- byte 0x94 gets read as
a right double quote in Windows-1252, silently closing strings
mid-parse. This caused run-enrollment.ps1 to fail on PXE-imaged
machines with "string is missing the terminator" at line 113.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:23:11 -04:00
cproudlock
fb5841eb20 run-enrollment: wait for PPKG provisioning before staging chain
Install-ProvisioningPackage is async — it queues the provisioning engine
and returns immediately. The actual BPRT app installs (Chrome, Office,
Tanium, CyberArk, etc.) run in the background. Without waiting, the
PPKG reboot fires while installs are still in progress, leaving apps
partially installed.

Fix: poll for C:\Logs\BPRT\Remove Staging Locations\Log.txt — the last
BPRT step. When that file exists, all provisioning steps have completed.
Polls every 10 seconds for up to 15 minutes (Office install can be slow).
Progress logged every 30 seconds showing which steps have finished.

If the timeout fires (15 min), logs a warning and proceeds — the SYSTEM
logon task from 06-OrganizeDesktop.ps1 provides self-healing on the next
boot for anything that was incomplete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:31:36 -04:00
cproudlock
6c76719a47 Logging, PCTypes, edge profiles for all types
Three final optimization batches:

1. Start-Transcript added to 4 scripts that lacked standalone logging:
   04-NetworkAndWinRM.ps1, 05-OfficeShortcuts.ps1, 01-eDNC.ps1,
   02-MachineNumberACLs.ps1. Each writes to C:\Logs\SFLD\<name>.log
   with append mode. Stop-Transcript added before exit points.

2. preinstall.json: Oracle Client PCTypes changed from ["*"] to
   ["Standard", "CMM", "Genspect", "Keyence", "WaxAndTrace", "Display"].
   Lab Workstations don't need Oracle Client (shopfloor data app
   dependency). VC++ redists stay at ["*"] (harmless shared deps).

3. Edge profiles added to all remaining PC types in site-config.json:
   CMM, Genspect, Keyence, WaxAndTrace, Standard-Timeclock all get the
   standard 3-tab setup (Plant Apps + Homepage + Dashboard) with
   homepage = tsgwp00524. Display-Lobby and Display-Dashboard get
   Shopfloor Dashboard as both homepage and single tab.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:57:22 -04:00
cproudlock
ed3bfc8234 Lab Workstation: profile-aware Edge homepage + startup tabs
Lab profile in site-config.json now has:
  edgeHomepage: http://tsgwp00524.logon.ds.ge.com/
  edgeStartupTabs: WJ Shop Floor Homepage, M365 Webmail, Shopfloor Dashboard

08-EdgeDefaultBrowser.ps1 now resolves edge config from:
  pcProfile.edgeStartupTabs > siteConfig.edgeStartupTabs > hardcoded
  pcProfile.edgeHomepage > first startup tab (existing behavior)

This lets different PC types have different Edge configs:
  Standard-Machine: Plant Apps + Homepage + Dashboard (homepage = Plant Apps)
  Lab: Homepage + Webmail + Dashboard (homepage = tsgwp00524)

Added webmail URL to site-config.json urls section:
  "webmail": "https://outlook.office365.us/mail"

Lab gets no OpenText/UDC/eDNC — already filtered:
  OpenText + UDC: PCTypes = ["Standard"] in preinstall.json
  eDNC: Standard/01-eDNC.ps1 (type-specific, never runs for Lab)
  Office: from PPKG (shared across all shopfloor types)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:49:43 -04:00
cproudlock
7c8eb6899d Shared machine-number helper, site-config for OpenText + PreInstall, placeholder type dirs
Three optimization batches from the pipeline audit:

1. Shared Update-MachineNumber.ps1 helper (lib/)
   Extracts duplicated machine-number update logic from Configure-PC.ps1,
   Check-MachineNumber.ps1, and Set-MachineNumber.ps1 into a shared
   dot-sourceable helper at Shopfloor/lib/Update-MachineNumber.ps1.

   Exports:
     Get-CurrentMachineNumber → @{ Udc = $string; Ednc = $string }
     Update-MachineNumber -NewNumber <n> [-Site <s>] → @{ UdcUpdated; EdncUpdated; Errors }

   All three consumers now dot-source the helper instead of duplicating
   ~50 lines each. Set-MachineNumber.ps1 also migrated from inline
   Get-SiteConfig to dot-sourcing Get-PCProfile.ps1 for consistency.

2. Site-config integration for remaining scripts
   Setup-OpenText.ps1: exclude lists (profiles + shortcuts) now read from
     site-config.json opentext section, falling back to West Jefferson
     defaults. Inline Get-SiteConfig since the script runs from
     C:\PreInstall\installers\opentext\ (can't dot-source Get-PCProfile).

   00-PreInstall-MachineApps.ps1: after parsing preinstall.json, scans
     InstallArgs for "West Jefferson" and replaces with site-config
     siteName if different. Inline Get-SiteConfig for same reason.

3. Placeholder type-specific directories
   Created skeleton 01-Setup-*.ps1 scripts for all PC types so the
   directory structure is in place and Run-ShopfloorSetup's type-specific
   loop has something to iterate over:
     Genspect/01-Setup-Genspect.ps1
     Keyence/01-Setup-Keyence.ps1
     WaxAndTrace/01-Setup-WaxAndTrace.ps1
     Lab/01-Setup-Lab.ps1
   Each logs a "no type-specific apps configured yet" banner and exits.
   Fill in app installs when details are finalized; for share-based
   installs, copy the CMM/01-Setup-CMM.ps1 pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:44:10 -04:00
cproudlock
b236b18fbc CMM: share-based installer framework with SFLD credential lookup
Refactored CMM/01-Setup-CMM.ps1 from local-file installer to network-
share-based pattern. CMM apps live on a file share instead of being
pre-staged locally or pulled from Azure Blob.

Framework:
  1. Reads share path from site-config.json CMM profile (cmmSharePath),
     falls back to West Jefferson default
  2. Scans HKLM:\SOFTWARE\GE\SFLD\Credentials\* for a credential entry
     whose TargetHost matches the share's server name
  3. Mounts the share as S: using net use with the stored creds
  4. (Placeholder) Install apps from the share
  5. Disconnects the share

The Get-SFLDCredential helper function is generic and will be reused by
Genspect/Keyence scripts when their share-based installs are built. It
matches credentials by TargetHost field, supporting exact match and
domain-suffix matching.

App install blocks are commented out as placeholders — uncomment when
PC-DMIS, CLM License, and other Hexagon app details are finalized.

Added cmmSharePath to site-config.json CMM profile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:24:22 -04:00
cproudlock
ed803539e0 PC profiles: per-type/sub-type config + Standard Timeclock/Machine menu
Adds a pcProfiles section to site-config.json that lets each PC type (and
optional sub-type) override startupItems, taskbarPins, and desktopApps.
Scripts resolve: pcProfile > site-wide default > hardcoded fallback.

New shared helper: Shopfloor/lib/Get-PCProfile.ps1
  Dot-sourced by consuming scripts. Reads pc-type.txt + pc-subtype.txt,
  builds a profile key (e.g. "Standard-Machine"), and looks it up in
  site-config.json pcProfiles. Exports $siteConfig, $pcType, $pcSubtype,
  $profileKey, $pcProfile for the caller to use.

  Replaces the inline Get-SiteConfig function that was copy-pasted into
  each script. Scripts now do:
    . "$PSScriptRoot\lib\Get-PCProfile.ps1"
  instead of duplicating the loader.

startnet.cmd changes:
  - Added Lab as PC type option (7)
  - Standard now has a sub-type menu: Timeclock / Machine
  - Display sub-type menu also writes PCSUBTYPE for consistency
  - pc-subtype.txt written alongside pc-type.txt when sub-type selected
  - site-config.json copied from enrollment share to W:\Enrollment\

site-config.json v2.0:
  - New pcProfiles section with profiles for:
    Standard-Timeclock, Standard-Machine, CMM, Genspect, Keyence,
    WaxAndTrace, Lab, Display-Lobby, Display-Dashboard
  - CMM/Genspect/Keyence/WaxAndTrace profiles have TODO comments for
    type-specific apps (placeholder with WJ Shopfloor baseline only)
  - Lab/Display profiles have empty startupItems and desktopApps
  - Top-level startupItems/taskbarPins/desktopApps remain as site-wide
    defaults (used when no profile matches)

Updated scripts:
  06-OrganizeDesktop.ps1 - desktopApps from profile > site > hardcoded
  07-TaskbarLayout.ps1   - taskbarPins from profile > site > hardcoded
  08-EdgeDefaultBrowser.ps1 - uses shared profile loader
  Configure-PC.ps1       - startupItems from profile > site > hardcoded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:19:51 -04:00
cproudlock
0aaf049942 Extract site-specific values to site-config.json
New site-config.json file at C:\Enrollment\ (staged by startnet.cmd from
the enrollment share) contains all West Jefferson-specific values that were
previously hardcoded across 7 scripts. To deploy at a different GE site,
clone site-config.json and change the values - scripts need zero changes.

Config schema (v1.0):
  siteName / siteNameCompact  - UDC/eDNC site args
  urls{}                      - Edge startup tab fallback URLs
  edgeStartupTabs[]           - ordered tab list with .url file basenames
  opentext{}                  - excluded .hep profiles and .lnk shortcuts
  startupItems[]              - Configure-PC toggle list (exe/existing/url)
  taskbarPins[]               - 07-TaskbarLayout pin order with lnk paths
  desktopApps[]               - 06-OrganizeDesktop Phase 2 app list

Every script uses the same inline Get-SiteConfig helper that reads the
JSON and returns $null if missing/corrupt. All consumers fall back to the
current hardcoded West Jefferson defaults when $siteConfig is null, so
PXE servers without a site-config.json continue working identically.

Scripts updated:
  06-OrganizeDesktop.ps1   - desktopApps array from config
  07-TaskbarLayout.ps1     - pinSpec array from config
  08-EdgeDefaultBrowser.ps1 - startup tab loop from config
  Configure-PC.ps1         - startup items + site name from config
  Check-MachineNumber.ps1  - site name from config
  Set-MachineNumber.ps1    - site name from config
  01-eDNC.ps1              - siteName + siteNameCompact from config
  startnet.cmd             - copies site-config.json from enrollment share

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:11:35 -04:00
cproudlock
45ff163eea Fix reboot race + dispatcher owns all reboots
Two related fixes from the pipeline audit:

1. Stage-Dispatcher race condition (critical):
   Run-ShopfloorSetup.ps1 called shutdown /r /t 10 and the dispatcher
   had to write the next stage + register RunOnce within that 10-second
   window. If disk I/O was slow, the reboot fired before RunOnce was
   registered, and the chain broke.

   Fix: dispatcher now cancels Run-ShopfloorSetup's pending reboot
   (shutdown /a) immediately after it returns, then advances the stage
   and registers RunOnce with no time pressure, then initiates its own
   shutdown /r /t 5.

2. Dispatcher owns all reboots:
   Run-ShopfloorSetup.ps1 now checks the -FromDispatcher flag at the
   end. When called by the dispatcher, it schedules shutdown /r /t 30
   as a safety net (the dispatcher cancels it immediately). When called
   standalone (manual run or legacy FirstLogonCommands), it reboots
   directly with /t 10 as before.

   This means the dispatcher has full control over the reboot lifecycle:
   cancel -> advance stage -> register RunOnce -> reboot. No racing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:58:57 -04:00
cproudlock
3494aa0554 Fix stage gate infinite loop: -FromDispatcher bypass
The stage-file gate in Run-ShopfloorSetup.ps1 would fire even when
called by Stage-Dispatcher.ps1 (because the stage file still contains
"shopfloor-setup"), causing an infinite exit loop.

Fix: Run-ShopfloorSetup now accepts -FromDispatcher switch. The gate
only fires when the switch is absent (i.e. when called by the unattend's
FirstLogonCommands). Stage-Dispatcher passes -FromDispatcher when
invoking Run-ShopfloorSetup, bypassing the gate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:20:20 -04:00
cproudlock
12bcc9b549 Stage gate in Run-ShopfloorSetup + sync retrigger 5min
Run-ShopfloorSetup.ps1 now checks for C:\Enrollment\setup-stage.txt at
the very top. If the stage file exists (written by run-enrollment.ps1),
the script exits immediately with "deferring to Stage-Dispatcher.ps1
on next logon". This prevents the unattend's FirstLogonCommands chain
from running Run-ShopfloorSetup in the same session as run-enrollment,
which was bypassing the entire staged reboot chain.

Without this gate:
  FirstLogonCommand #1: run-enrollment.ps1 (sets stage file + RunOnce)
  FirstLogonCommand #2: Run-ShopfloorSetup.ps1 (runs immediately, ignoring stage)
  PPKG reboot fires after both complete
  Next boot: dispatcher has nothing to do (Run-ShopfloorSetup already ran)

With the gate:
  FirstLogonCommand #1: run-enrollment.ps1 (sets stage file + RunOnce)
  FirstLogonCommand #2: Run-ShopfloorSetup.ps1 (sees stage file, exits)
  PPKG reboot fires
  Next boot: RunOnce fires dispatcher, reads "shopfloor-setup", runs
  Run-ShopfloorSetup properly (stage file deleted by gate on re-entry)

Also: Monitor-IntuneProgress.ps1 RetriggerMinutes bumped from 3 to 5.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:19:34 -04:00
cproudlock
b13e34c05a Imaging chain: Stage-Dispatcher + PPKG reboot + unattended sync_intune
Replaces the single-session "cancel PPKG reboot and cram everything into
one autologon" flow with a staged chain where each reboot advances to the
next step automatically. The technician touches the keyboard 3 times total
(UNPLUG prompt, Y to reboot, Configure-PC selections).

New Stage-Dispatcher.ps1:
  Reads C:\Enrollment\setup-stage.txt and chains through:
    shopfloor-setup -> sync-intune -> configure-pc
  Each stage re-registers HKLM RunOnce so the dispatcher fires again on
  the next logon. Stage file is deleted when the chain completes.
  Transcript logged to C:\Logs\SFLD\stage-dispatcher.log.

  Stage "shopfloor-setup": runs Run-ShopfloorSetup.ps1 (which reboots via
    shutdown /r /t 10). Dispatcher advances stage to sync-intune in the
    ~10 second window before the machine goes down, re-registers RunOnce.

  Stage "sync-intune": launches Monitor-IntuneProgress.ps1 -Unattended.
    Exit 2 (pre-reboot done, user confirmed): dispatcher re-registers
    RunOnce and initiates shutdown /r /t 5. Stage stays at sync-intune so
    the monitor picks up post-reboot state on next boot.
    Exit 0 (post-reboot install complete): dispatcher chains directly to
    Configure-PC.ps1 in the same session, then deletes the stage file.

  Stage "configure-pc": runs Configure-PC.ps1 and deletes the stage file.
    Fallback entry point if the post-reboot chain was interrupted.

Modified run-enrollment.ps1:
  Removed the shutdown /a that canceled the PPKG reboot. Instead writes
  setup-stage.txt = "shopfloor-setup" and registers RunOnce for the
  dispatcher. PPKG reboot fires naturally (handles PendingFileRename
  operations like Zscaler rename and PPKG self-cleanup). Now tracked in
  the git repo at playbook/shopfloor-setup/run-enrollment.ps1.

Modified Monitor-IntuneProgress.ps1:
  New -Unattended switch. When set:
    Invoke-SetupComplete exits 0 without waiting for keypress.
    Invoke-RebootPrompt exits 2 without prompting or rebooting (dispatcher
    handles both). Manual sync_intune.bat usage (no flag) unchanged.
  RetriggerMinutes bumped from 3 to 5 (user request).

Modified startnet.cmd:
  Now also copies Stage-Dispatcher.ps1 from the PXE server to
  W:\Enrollment\Stage-Dispatcher.ps1 alongside run-enrollment.ps1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:55:00 -04:00
cproudlock
7c26e10f7e sync_intune: gate reboot prompt on Phase 1+2+3 pre-reboot completion
Don't prompt the user to reboot until the enrollment pipeline has
finished its pre-reboot work. Previously Test-RebootState fired as
soon as DSCDeployment.log showed "completed", even if Phase 1 (Identity)
or Phase 2 (SFLD config) checks were still in progress.

Now the reboot prompt requires ALL of these to be green in the snapshot:
  Phase 1: AzureAdJoined, IntuneEnrolled, EmTaskExists, PoliciesArriving
  Phase 2: SfldRoot, FunctionOk, SasTokenOk
  Phase 3: DeployLogExists, DeployComplete

This prevents the edge case where DSCDeployment.log completes but the
user reboots before Intune policies have fully landed, which could leave
the post-reboot DSC install phase without the SAS token or function
assignment it needs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:31:11 -04:00
cproudlock
e17b3a521d Fix 5 bugs from shopfloor-setup transcript review
1. UDC JSON ACL: set on directory C:\ProgramData\UDC\ with
   ContainerInherit+ObjectInherit instead of the file. UDC_Setup.exe
   gets killed by KillAfterDetection before UDC.exe creates
   udc_settings.json, so the file doesn't exist at ACL-grant time.
   Directory-level ACL with inheritance covers any file created later.

2. Set-MachineNumber.ps1 auto-running: the type-specific loop's
   Get-ChildItem -Filter "*.ps1" picked up the desktop tool alongside
   the numbered installer scripts. Added Where-Object { $_.Name -match
   '^\d' } so only numbered-prefix scripts (01-eDNC, 02-ACLs) run.

3. WJ Shopfloor copy-to-self: Phase 1 sweep moved WJ Shopfloor.lnk
   into Shopfloor Tools\, then Phase 2's Find-ExistingLnk found it
   there and tried to Copy-Item to the same path. Now checks if
   resolved source path == destination and prints "exists: (already
   in Shopfloor Tools)" instead of erroring.

4. NTLARS missing from taskbar pins: the $pinSpec entry was never
   added to 07-TaskbarLayout.ps1 despite the comment update. Added
   between eDNC and Defect_Tracker in pin order.

5. shutdown /a stderr noise: 15+ red "Unable to abort system shutdown"
   lines in the transcript from shutdown.exe writing to stderr when no
   shutdown is pending. Changed all occurrences in Run-ShopfloorSetup,
   00-PreInstall-MachineApps to: cmd /c "shutdown /a 2>nul" *>$null
   which suppresses both native stderr and PS error stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:28:25 -04:00
cproudlock
cb2a9d48a1 Shopfloor: Configure-PC tool, machine-number logon prompt, execution order fixes
New tools:

Configure-PC.bat/.ps1 - Interactive desktop tool for SupportUser to
configure a shopfloor PC after imaging. Two sections:
  1. Machine number: if UDC/eDNC are still at placeholder 9999, prompt
     to set the real number right now (updates UDC JSON + eDNC registry,
     restarts UDC.exe with new args).
  2. Auto-startup toggle: pick which apps start at user logon from a
     numbered list (UDC, eDNC, Defect Tracker, WJ Shopfloor, Plant Apps).
     Creates/removes .lnk files in AllUsers Startup folder. Toggle UI
     shows [ON]/[  ] state, safe to re-run anytime. Plant Apps URL
     resolved from .url file at runtime with hardcoded fallback to
     https://mes-wjefferson.apps.lr.geaerospace.net/run/...
  3. Item 6 in the toggle list: register/unregister a "Check Machine
     Number" logon task for standard (non-admin) users. When enabled,
     the task fires at every logon, checks for 9999, pops an InputBox
     if found, updates both apps, then unregisters itself on success.

Check-MachineNumber.ps1 - The logon task script. Runs as the logged-in
user (needs GUI for InputBox), not SYSTEM. Writing to ProgramData + HKLM
is possible because 02-MachineNumberACLs.ps1 pre-grants BUILTIN\Users
write access on the two specific targets during imaging.

02-MachineNumberACLs.ps1 - Standard type-specific script (runs after
01-eDNC.ps1). Opens C:\ProgramData\UDC\udc_settings.json for Users:Modify
and HKLM:\...\GE Aircraft Engines\DNC\General for Users:SetValue. Narrow
scope, not blanket admin.

Execution order fixes in Run-ShopfloorSetup.ps1:

The dispatcher now has two lists: $skipInBaseline (scripts NOT run in the
alphabetical baseline loop) and $runAfterTypeSpecific (scripts run
explicitly after type-specific scripts complete). This fixes the bug where
06/07 ran before 01-eDNC.ps1 installed DnC, so eDNC/NTLARS shortcuts were
silently skipped.

New execution order:
  Baseline: 00-PreInstall, 04-NetworkAndWinRM (skipping 05-08 + tools)
  Type-specific: 01-eDNC, 02-MachineNumberACLs
  Finalization: 06-OrganizeDesktop, 07-TaskbarLayout

06 internally calls 05 (Office shortcuts, Phase 0) and 08 (Edge config,
Phase 4) as sub-phases, so they also benefit from running late. Office
isn't installed until after the first reboot (ppkg streams C2R), so 05
no-ops at imaging time but succeeds when 06's SYSTEM logon task re-runs
it on the second boot. 08 resolves startup-tab URLs from .url files
delivered by DSC (even later); same self-heal via the logon task.

Other fixes in this commit:

- OpenText Setup-OpenText.ps1 Step 4: exclude WJ_Office.lnk, IBM_qks.lnk,
  mmcs.lnk desktop shortcuts (matching the Step 3 .hep profile exclusion
  from the previous commit). Removes stale copies from prior installs.
- 05-OfficeShortcuts.ps1: widened Office detection to 6 path variants
  covering C2R + MSI + Office15/16, with diagnostic output on miss.
- 06-OrganizeDesktop.ps1: removed Phase 3 (desktop-root pin copies for
  eDNC/NTLARS) so shortcuts live in Shopfloor Tools only, not duplicated
  at root. Emptied $keepAtRoot. Added Phase 0 (call 05) and Phase 4
  (call 08). Lazy folder creation + empty-folder cleanup. Scheduled task
  now runs as SYSTEM (was BUILTIN\Users with Limited which failed the
  admin check). Added NTLARS to 07's taskbar pin list.
- 08-EdgeDefaultBrowser.ps1: Plant Apps URL fallback hardcoded from
  device-config.yaml.
- All new scripts have Start-Transcript logging to C:\Logs\SFLD\ with
  timestamps and running-as identity.
- Run-ShopfloorSetup.ps1: Start-Transcript + Stop-Transcript wrapping
  entire dispatcher run, writes to C:\Logs\SFLD\shopfloor-setup.log.
  Configure-PC.bat added to SupportUser desktop copy list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:44:28 -04:00
cproudlock
900180cd12 Shopfloor: desktop folder org, taskbar pins, Edge defaults
Three new baseline scripts that run during shopfloor imaging to clean up
the end-user Public Desktop. Before this, Azure AD users logged into a
shopfloor PC and saw 20+ loose shortcuts at the desktop root (Office
apps, OpenText sessions, WJ web portals, DNC utilities, Defect Tracker,
plus .url files for every intranet page) with no organization. End users
couldn't find anything.

06-OrganizeDesktop.ps1 - Single source of truth for Public Desktop layout
  Phase 1: sweeps loose shortcuts at the desktop root into three category
    folders - Office\, Shopfloor Tools\, Web Links\ - by filename regex,
    extension, and .lnk target resolution. Allowlists eDNC.lnk and
    NTLARS.lnk to stay at root since end users click them too often.
    Unknown items are left at the root on purpose (never delete).
  Phase 2: materializes specific app shortcuts into Shopfloor Tools\.
    UDC / eDNC / NTLARS are built fresh from their .exe paths; WJ
    Shopfloor and Defect_Tracker are MSI-advertised (empty TargetPath,
    Darwin descriptor) so we copy the existing .lnk from wherever it
    lives via a multi-location lookup. Each entry is conditional on its
    source being present - script runs cleanly on PC types without DnC.
  Phase 3: drops eDNC.lnk and NTLARS.lnk at desktop root from the
    Shopfloor Tools\ copies, so end users have both a folder version
    and a quick-access root version.
  Phase 4: registers an "Organize Public Desktop" scheduled task that
    re-runs phase 1 at every logon. Shortcuts added later by DSC /
    Intune / msiexec get filed automatically without another imaging
    pass. Admin check at the top, -ErrorAction Stop on Register-
    ScheduledTask and directory creation so failures are caught
    instead of printing false success.

07-TaskbarLayout.ps1 - Minimal taskbar pinner
  Checks which .lnk files 06 created in Shopfloor Tools\, then writes
  LayoutModification.xml to the Default User profile with taskbar pins
  in order: Edge, WJ Shopfloor, UDC, eDNC, Defect_Tracker. No shortcut
  creation in this script - all shortcut management lives in 06.
  Missing .lnks are skipped (PC types without DnC just get fewer pins).
  Applies on first logon of new user profiles (Azure AD users after
  enrollment). Existing profiles don't re-read Default User - Windows
  design limitation since 1703, no programmatic fix.

08-EdgeDefaultBrowser.ps1 - Edge as default browser + startup tabs
  Motivated by the ppkg installing Chrome alongside Edge: new Azure AD
  users hit a "Choose your default app" picker on first URL click
  because nothing is marked default. Two layers:
    1. dism /Online /Import-DefaultAppAssociations:<xml> writes an XML
       with Edge ProgIds for http/https/.htm/.html/.pdf/.svg/.webp into
       the Default User profile template. New profiles inherit.
    2. HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\
       DefaultAssociationsConfiguration registry value (the "Set a
       default associations configuration file" GPO) points at the same
       XML so Windows re-applies on every logon, catching Windows-update
       defaults-reset cases.
  Leaves Chrome installed, just not the default URL handler.

  Also sets Edge startup tabs via machine-wide policies under
  HKLM:\SOFTWARE\Policies\Microsoft\Edge:
    RestoreOnStartup      = 4 (open specific URLs)
    RestoreOnStartupURLs  = Plant Apps, WJ Shop Floor Homepage, Shopfloor
                            Dashboard (tab order per spec)
    HomepageLocation      = first tab (Plant Apps)
    HomepageIsNewTabPage  = 0
    ShowHomeButton        = 1
  URLs are resolved dynamically from the .url files on the Public
  Desktop (or Web Links\ after the sweep), so if WJDT changes a URL
  later the script picks it up without a code change. Fallbacks are
  hardcoded for the two portals we have URLs memorized for; Plant Apps
  has no fallback and will be skipped if the .url file is missing.

Test workflow: admin-check in all three scripts fails fast on non-
elevated runs instead of spamming half-successful Access Denied output
like the first draft did.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:38:38 -04:00
cproudlock
c464f45f4f Shopfloor sync_intune + Set-MachineNumber hardening
Long debugging round on the shopfloor test PC with several overlapping
bugs. This commit folds all the fixes together.

sync_intune.bat
- Slim down to an elevation thunk that launches a NEW elevated PS
  window via Start-Process -Verb RunAs (with -NoExit so the window
  doesn't vanish on error). All UI now lives in the PS monitor, not
  mixed into the cmd launcher.
- Goto-based control flow. Earlier version had nested if (...) blocks
  with literal parens inside echo lines (e.g. "wrappers (Install-eDNC,
  ...etc)."); cmd parses if-blocks by counting parens character-by-
  character, so the ")" in "etc)." closed the outer block early and
  the leftover "." threw ". was unexpected at this time.", crashing
  the elevated cmd /c window before pause ran.
- Multi-location Monitor-IntuneProgress.ps1 lookup so the user's
  quick-test workflow (drop both files on the desktop) works without
  manually editing the hardcoded path. Lookup order:
    1. %~dp0lib\Monitor-IntuneProgress.ps1
    2. %~dp0Monitor-IntuneProgress.ps1
    3. C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1
    4. C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1
- Prints "Launching: <path>" as its first line so you can see which
  copy it actually loaded. This caught a bug where a stale desktop
  copy was shadowing the canonical file via fallback #2.

Set-MachineNumber.bat
- Same multi-location lookup pattern. Old version used
  %~dp0Set-MachineNumber.ps1 and bombed when the bat was copied to
  the desktop without its .ps1 sibling.
- Goto-based dispatch, no nested parens, for the same parser reason.

Monitor-IntuneProgress.ps1
- Start-Transcript at the top, writing to C:\Logs\SFLD\ (falls back
  to %TEMP% if C:\Logs\SFLD isn't writable yet) with a startup banner
  including a timestamp. Every run leaves a captured trace.
- Main polling loop wrapped in try/catch/finally. Unhandled exceptions
  print a red report with type, message, position, and stack trace,
  then block on Wait-ForAnyKey so the window can't auto-close on a
  silent crash.
- Console window resize at startup via $Host.UI.RawUI.WindowSize /
  BufferSize, wrapped in try/catch (Windows Terminal ignores it, but
  classic conhost honors it).
- Clear-KeyBuffer / Read-SingleKey / Wait-ForAnyKey helpers. Drain any
  buffered keystrokes from the polling loop before each prompt so an
  accidental keypress can't satisfy a pause prematurely.
- Invoke-SetupComplete / Invoke-RebootPrompt final-state handlers.
  The REBOOT REQUIRED branch now shows a yellow 3-line header, a
  four-line explanation, and a cyan "Press Y to reboot now, or N to
  cancel:" prompt via Read-SingleKey @('Y','N'). Y triggers
  Restart-Computer -Force (with shutdown.exe fallback), N falls
  through to Wait-ForAnyKey.
- Display order: status table FIRST, QR LAST. The cursor ends below
  the QR so the viewport always follows it - keeps the QR on screen
  regardless of window height. Works on both classic conhost and
  Windows Terminal (neither reliably honors programmatic resize).
- Half-block QR renderer: walks QRCoder's ModuleMatrix directly and
  emits U+2580 / U+2584 / U+2588 / space, one output line per two
  matrix rows. Halves the rendered height vs AsciiQRCode full-block.
  Quiet zone added manually via $pad=4 since QRCoder's ModuleMatrix
  doesn't include one. Trade-off: may not be perfectly square on all
  fonts, but the user accepted that for the smaller footprint after
  multiple iterations comparing full-block vs half-block vs PNG popup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:30:12 -04:00
cproudlock
cd00d6d2e1 OpenText: track Setup-OpenText scripts in repo, opt-in KillAfterDetection
Two related fixes from a debugging round on the test PC:

1. PreInstall runner: detection-during-install kill is now opt-in via
   "KillAfterDetection: true" on JSON entries that need it. Old behavior
   killed any installer as soon as its detection passed - which broke
   Oracle: Oracle creates its registry key partway through install,
   the runner detected it at the 25s poll, killed msiexec mid-install,
   and msiserver was still doing rollback when the next install (VC++
   2008) started - so VC++ 2008 hit ERROR_INSTALL_ALREADY_RUNNING
   (1618). Only UDC needs the detection-kill (its installer spawns a
   hidden WPF window and never exits). Other installers exit cleanly
   on their own and shouldn't be killed.

2. Track Setup-OpenText scripts in git. The bundled OpenText install
   scripts (Setup-OpenText.ps1, Setup-OpenText.cmd, version.txt) live
   at runtime in /home/camp/pxe-images/main/dependencies/opentext/
   alongside the binary install files (~106 MB of MSI/CAB/MSP/MST plus
   profile content). The binaries stay outside git but the script
   logic and version stamp are mirrored into playbook/preinstall/
   opentext/ here so git history captures changes to the install
   logic and version bumps. README.md explains the workflow.

   Latest Setup-OpenText.ps1 includes:
     - $SourceDir default moved into script body (PowerShell evaluates
       param([string]$X = $PSScriptRoot) defaults at parameter-binding
       time, when $PSScriptRoot may not yet be populated, so the
       default came out as empty string and Join-Path crashed)
     - Logging set up FIRST so any startup error gets captured
     - REBOOT=ReallySuppress dropped from both msiexec calls (base MSI
       and SP1 patch) - OpenText installs shell extensions that hook
       explorer.exe, and Restart Manager closes explorer to replace
       the shell DLLs. With REBOOT=ReallySuppress, RM closed explorer
       but interpreted the relaunch as a "reboot action" and refused
       to do it, leaving the user with no desktop. /norestart on its
       own prevents the actual Windows reboot but lets RM cleanly
       close-and-relaunch explorer mid-install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 12:08:07 -04:00
cproudlock
5eacd1d596 PreInstall runner: surface installer log on EXE failures (LogFile field)
When the runner runs a Type:MSI install it injects /L*v <log> and tails
that log on failure to show what actually went wrong. Type:EXE installs
had no equivalent - if Setup-OpenText.cmd or any other EXE wrapper
failed, the installlog just showed "Exit code 1 - FAILED" with no clue
what happened inside.

Adds an optional LogFile field to JSON entries. When present on a
Type:EXE entry, the runner:
  - Logs "Installer log: <path>" before launching the installer
  - On failure, tails the last 30 lines of that file into the runner
    log (same pattern as the MSI verbose log scan)

Wired up on the OpenText entry to point at C:\Logs\PreInstall\Setup-
OpenText.log (which Setup-OpenText.ps1 already writes itself). Other
EXE entries can opt in by adding their own LogFile field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:19:45 -04:00
cproudlock
a33a115394 Move Monitor-IntuneProgress.ps1 to lib/ - it was hanging the dispatcher
Run-ShopfloorSetup.ps1 line 46-47 does:

  Get-ChildItem -Path $baselineDir -Filter "*.ps1" -File | Sort-Object Name
  foreach ($script in $scripts) { & $script.FullName }

This picks up EVERY *.ps1 in Shopfloor\ and runs it as a baseline
script. Last commit (66d13d8) put Monitor-IntuneProgress.ps1 in that
same directory, which means the dispatcher was running it as the LAST
baseline script (M sorts after 00/04/05). The monitor is an infinite
poll loop that never returns until the SFLD lifecycle is complete -
so the dispatcher hung there forever, and Standard\01-eDNC.ps1 and
Standard\Set-MachineNumber.ps1 never ran.

Symptoms in the test run:
  - 00-PreInstall-MachineApps.ps1 ran (10 installed, 1 OpenText fail)
  - 04-NetworkAndWinRM.ps1 ran silently
  - 05-OfficeShortcuts.ps1 ran silently
  - Monitor-IntuneProgress.ps1 started (Clear-Host + status table) and
    hung in its main loop
  - eDNC + Set-MachineNumber never ran

Fix: move Monitor-IntuneProgress.ps1 into Shopfloor\lib\ so the
dispatcher's non-recursive Get-ChildItem doesn't see it. Update
sync_intune.bat's MONITOR path to the new location, and add a
comment explaining WHY the monitor lives under lib\ to prevent this
mistake from being repeated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:19:09 -04:00
cproudlock
66d13d8ad0 sync_intune: rewrite as 5-phase status monitor with reboot detection
Replaces the 3-step pass/fail polling that lived in the .bat with a
PowerShell monitor that renders a full status table for the SFLD
enrollment lifecycle and handles the pre-reboot -> reboot -> post-reboot
transition explicitly.

Three structural problems with the old script:

1. Step 3 ("SFLD - Consume Credentials task exists") fired too early.
   The task is created by SetupCredentials.log around 08:52 in the
   pre-reboot phase, NOT post-reboot, so passing all 3 gates didn't
   actually mean "fully done" - it just meant "credential setup ran".

2. No detection of the pre-reboot -> reboot -> post-reboot transition.
   The script never read DSCDeployment.log, so it couldn't tell the
   user "you need to reboot now to start the install phase". A device
   stuck waiting for reboot was indistinguishable from one still
   syncing.

3. No visibility into Phase 4 (per-script wrappers like Install-eDNC,
   Install-UDC, Install-VCRedists, Install-OpenText). When something
   hung you had to manually grep C:\Logs\SFLD\.

New layout:

  sync_intune.bat - thin launcher (~50 lines): self-elevate, invoke
                    Monitor-IntuneProgress.ps1, branch on exit code
                    (0 = done / 2 = reboot needed / else = error).

  Monitor-IntuneProgress.ps1 - the actual monitor (~340 lines):
    - 5-phase status table (Identity / SFLD config / DSC deployment +
      install / Custom scripts / Final) updated every 30s via Clear-
      Host + redraw, with the QR code anchored at the top.
    - Phase 4 auto-discovers custom scripts by parsing DSCInstall.log
      for "Downloading script: <name>" lines AND scanning C:\Logs\SFLD\
      Install-*.log files - so Display PCs running entirely different
      scripts surface their own list automatically without hardcoding.
      Statuses: pending / running / done / failed (mtime + tail-based).
    - Boot-loop-safe reboot detection via Test-RebootState: only signals
      'needed' if DSCDeployment.log was modified AFTER LastBootUpTime.
      Once we've rebooted past it, just waits for DSCInstall.log.
    - Caches monotonic Phase 1 indicators (AzureAdJoined, IntuneEnrolled,
      EnterpriseMgmt task) so dsregcmd /status (slow ~1-2s) only runs
      until the flag flips true, not on every poll.
    - Triggers Intune sync at startup, re-triggers every 3 minutes (was
      every 15 seconds in the old loop, which actively interrupted
      in-flight CSP work).

  Exit codes consumed by sync_intune.bat:
    0 - DSCInstall.log shows "Installation completed successfully"
    2 - DSCDeployment.log shows "Deployment completed successfully" AND
        the deploy log is newer than LastBootUpTime (= reboot needed)
    1 - error

Detection markers (decoded from a captured run at /home/camp/pxe-images/
Logs/ - see comment block at top of Monitor-IntuneProgress.ps1).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:56:20 -04:00
cproudlock
453b42a159 Shopfloor: 05-OfficeShortcuts.ps1 baseline (Excel/Word/PowerPoint)
Office Click-to-Run installs the binaries when an Office-bearing ppkg
is selected (e.g. GCCH_Prod_SFLD_StdOffice-x86_*) but doesn't create
desktop shortcuts - operators only see Office in the Start Menu's
Microsoft 365 folder. This baseline script fills that gap.

Self-detects Office by EXE existence at C:\\Program Files\\Microsoft
Office\\root\\Office16\\ or the (x86) equivalent. No Office found =
silent no-op, so it's safe to run on every PC type (Display kiosks,
Wax/Trace, Keyence, etc.) without needing a per-type filter.

Creates Excel.lnk / Word.lnk / PowerPoint.lnk in two places:
- C:\\Users\\Public\\Desktop\\  - visible to all users immediately
- C:\\Users\\Default\\AppData\\Roaming\\Microsoft\\Windows\\Start
  Menu\\Programs\\  - inherited by every NEW user profile created
  on the device (Azure AD operator logons after enrollment)

Numbered 05- so it runs after 00-PreInstall and 04-NetworkAndWinRM
in the Shopfloor baseline sequence. Idempotent - WScript.Shell's
CreateShortcut overwrites existing .lnks each run.

Outlook / OneNote / Access / Publisher intentionally not shortcutted
(scope decision; can be added by extending the $officeApps array).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:11:08 -04:00
cproudlock
7a27f1a0a1 sync_intune.bat: longer poll interval, in-place status, no sync flood
Two related fixes for the desktop helper:

1. Stop hammering the Intune sync trigger every 15 seconds. The old
   loop called :do_sync (Start-ScheduledTask on Schedule #3) on every
   failed check, which started a fresh CSP pull before the previous
   one had time to complete - the Intune engine treats a re-trigger
   as "start over" and kills in-flight policy application work, so
   nothing ever finished. New cadence: trigger sync once at the start
   of each step, then poll every 30 s, only re-trigger every 6 polls
   (~3 min). POLL_SECS and RETRIGGER_POLLS are top-of-script knobs.

2. Stop pushing the QR code off the top of the window. The old loop
   echoed "Checking again in 15s..." on a new line every iteration,
   so after a few minutes the QR code (which contains the device ID
   the operator scans) had scrolled out of view. Replaced the per-
   iteration echo with a single self-redrawing status line using a
   captured CR character (copy /Z trick) and <nul set /p, padded to
   clear leftover characters. Important transitions ("Re-triggering
   sync...", "[DONE] ...") still print echo. lines so they survive in
   the scrollback as permanent history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:10:51 -04:00
cproudlock
564f14ffcf PreInstall runner: capture real exit codes, surface MSI errors
Three observability fixes that made the VC++ MSI failures actually
debuggable instead of showing "Exit code  - FAILED" with an empty
value for every install:

1. Switch from Start-Process -PassThru (without -Wait) to
   [System.Diagnostics.Process]::Start() with a ProcessStartInfo.
   PowerShell 5.1 has a known bug where Start-Process disposes the
   Process object's OS handle when control returns to the script,
   so $proc.ExitCode reads as $null even after WaitForExit() - which
   was causing every MSI install to be reported as failed regardless
   of the actual result.

2. Pass /L*v <log> to msiexec on every MSI install so we get a full
   verbose log per app at C:\Logs\PreInstall\msi-<safename>.log.

3. On install failure, scan the verbose log for *meaningful* lines
   (Note: 1: <code>, "return value 3", custom action errors, "Failed
   to", "Installation failed", common 2xxx error codes) instead of
   tailing the last 25 lines, which is rollback/cleanup noise. This
   surfaces the actual root-cause line directly in the runner log so
   you don't have to dig through C:\Logs to diagnose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:08:42 -04:00
cproudlock
ded0a7184b Shopfloor scripts: em-dash to hyphen, add positional Level param
Replace em-dash characters with plain hyphens across the 5 shopfloor
setup scripts (avoids cp1252 mojibake in .bat files and keeps the
PowerShell sources consistent). Also adds [Parameter(Position=1)] to
Write-PreInstallLog so the Level argument can be passed positionally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:24:37 -04:00
cproudlock
a1a78e2ba3 PXE preinstall pipeline + Set-MachineNumber helper for Standard PCs
Adds a local-install pipeline so Standard shopfloor PCs get Oracle, the
VC++ redists (2008-2022), and UDC installed during PXE imaging via Samba
instead of pulling ~215 MB per device from Azure blob over the corporate
WAN. Intune DSC then verifies (already-installed apps are skipped) and
the only Azure traffic on the happy path is ~11 KB of CustomScripts
wrapper polling.

New files:
- playbook/preinstall/preinstall.json — curated app list with PCTypes
  filter and per-app detection rules. Install order puts VC++ 2008
  LAST so its (formerly) reboot-triggering bootstrapper doesn't kill
  the runner mid-loop. (2008 itself now uses extracted vc_red.msi with
  REBOOT=ReallySuppress; the reorder is defense in depth.)
- playbook/shopfloor-setup/Shopfloor/00-PreInstall-MachineApps.ps1 —
  the runner. Numbered 00- so it runs first in the baseline sequence.
  Reads preinstall.json, filters by PCTYPE, polls for completion via
  detection check (handles UDC's hung WPF process by killing it once
  detection passes), uses synchronous WriteThrough logging that
  survives hard reboots, preserves log history across runs.
- playbook/shopfloor-setup/Standard/Set-MachineNumber.{ps1,bat} — desktop
  helper for SupportUser. Reads current UDC + eDNC machine numbers,
  prompts via VB InputBox, validates digits-only, kills running UDC,
  edits both C:\ProgramData\UDC\udc_settings.json and HKLM\…\GE Aircraft
  Engines\DNC\General\MachineNo, relaunches UDC. Lets a tech assign a
  real machine number to a mass-produced PC without admin/LAPS.
- playbook/sync-preinstall.sh — workstation helper to push installer
  binaries from /home/camp/pxe-images/main/ to the live PXE Samba.

Changes:
- playbook/startnet.cmd + startnet-template.cmd — add xcopy to stage
  preinstall bundle from Y:\preinstall\ to W:\PreInstall\ during the
  WinPE imaging phase, gated on PCTYPE being set.
- playbook/pxe_server_setup.yml — create /srv/samba/enrollment/preinstall
  + installers/ directories and deploy preinstall.json there.
- playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 — bump AutoLogonCount
  to 99 at start (defense against any installer triggering an immediate
  reboot mid-dispatcher; final line still resets to 2 on successful
  completion). Copy Set-MachineNumber.{ps1,bat} to SupportUser desktop
  on Standard PCs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:06:26 -04:00
cproudlock
9c54307b1b Shopfloor cleanups: drop OpenText CSF + MarkZebra, gitignore eMxInfo
- Delete 02-OpenTextCSF.ps1 (CSF profile delivery moved to Intune YAML's
  CopyFiles section in main/device-config.yaml — no longer needed at the
  PXE/baseline layer)
- Strip MarkZebra install + post-config from 01-eDNC.ps1 (no longer
  needed; only eDNC core install + Dnc x86→x64 mirror + Site reg + eMxInfo
  deployment remain). Section numbering tightened.
- Add SITESELECTED="West Jefferson" to eDNC msiexec args so the MSI's
  site-specific Components (NtLarsWjfRegComp — FTP/FMS/PPDCS hosts +
  credentials) actually install. Without it, only the bare Site value was
  being set and all the connection details were unconfigured.
- gitignore: blanket-block any **/eMxInfo*.txt from being committed —
  the file contains obfuscated eDNC site credentials and must never go
  in git. Canonical source lives at /home/camp/pxe-images/main/eMxInfo.txt
  outside the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:05:52 -04:00
cproudlock
05fa74574a Intune sync: 3-step lockdown monitor, fix batch detection, remove backup_lockdown
sync_intune.bat now monitors three stages sequentially:
1. SFLD registry key (device configuration received)
2. DSCInstall.log success string (DSC installation complete)
3. SFLD - Consume Credentials scheduled task (lockdown complete)
Triggers Intune sync before each poll. Prompts reboot on completion.

Fixed batch delayed expansion bugs, removed nested if/goto blocks.
Removed backup_lockdown.bat and its desktop copy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:52:31 -04:00
cproudlock
e3f2bbc6a5 Add QR code display of Intune device ID to sync tool
Bundles QRCoder.dll (184KB, .NET 4.0) to render the Azure AD device
GUID as a scannable QR code in the console when sync_intune.bat runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:15:20 -04:00
cproudlock
1ba4cce80f Remove shopfloor power and display settings script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:46:38 -04:00
cproudlock
a570efda71 Remove shopfloor Start Menu shortcuts script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:45:21 -04:00
cproudlock
9912b044a3 Shopfloor: single autologon, clear Start pins, Intune sync tool, update docs
- AutoLogonCount reduced from 2 to 1 in Run-ShopfloorSetup.ps1
- Remove default pinned Start Menu tiles and set blank layout for future users
- Add sync_intune.bat: triggers MDM sync and polls for SFLD group policies
- Update README.md and SETUP.md with current project state (boot chain, new
  scripts, samba shares, webapp pages, commit history)

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