Commit Graph

84 Commits

Author SHA1 Message Date
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
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
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
c98bbe6532 Remove last em-dash from OpenText README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:38:58 -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
b91a0a4bb7 OpenText: skip WJ_Office, IBM_qks, mmcs .hep profiles
Per West Jefferson request, those three connection profiles aren't used
on shopfloor PCs and just clutter the HostExplorer session picker.
They stay in the bundled source tree (dependencies/opentext/Profile/)
for rollback, we just don't copy them into the runtime destinations.

Implementation:
- New optional Exclude list on $contentMap entries
- Copy-HummingbirdContent filters files through Exclude before copying
- Also removes any stale excluded files from the destination up-front,
  so a PC that got them from an older install gets cleaned up on
  re-deploy (defensive - no production PC has the 15.0.SP1.2 marker
  yet so this won't actually fire in practice)
- NO version bump: 15.0.SP1.2 stays, per explicit request. First
  imaging run picks up the new logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:37:52 -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
a363fa31c0 OpenText: migrate from Intune Win32 LOB to PXE PreInstall + DSC
OpenText HostExplorer ShopFloor was previously delivered as an Intune
Win32 LOB app that ran the inner OpenTextHostExplorer15x64.msi directly,
which (a) skipped the [Files] section of the WJDT-built Inno Setup
wrapper that deploys profile/keymap/menu/macro files, and (b) deployed
desktop shortcuts pointing at C:\\GE Aerospace\\Hummingbird\\ - a path
HostExplorer doesn't search, so the profile would open from the desktop
shortcut but the keymaps and macros never got picked up.

This commit moves the install to the PXE PreInstall pipeline so it
gets baked into every Standard PC during imaging instead of being
pulled per-device by Intune. The DSC side ships separately as
Setup-OpenText.ps1 + Install-OpenText.ps1 wrapper in the
pxe-images/main/ tree (uploaded to Azure Blob).

preinstall.json: new entry for OpenText pointing at
opentext\\Setup-OpenText.cmd, a tiny launcher in the bundled subtree
that hands off to Setup-OpenText.ps1 (the runner only handles MSI/EXE
types). No DetectionMethod fields - Setup-OpenText.ps1 reads version
from version.txt next to itself and short-circuits via its own
HKLM\\SOFTWARE\\GE\\OpenText\\Installed marker check, so the version
constant lives in exactly one place (version.txt). Trade-off: ~1s
PowerShell launch on every up-to-date runner pass instead of a
zero-cost registry compare, in exchange for never having to bump
the version in multiple places.

sync-preinstall.sh: added dependencies/opentext to TREE_SUBDIRS so
the whole bundle (base MSI + cab + SP1 patch + ShopFloor transform +
profile/accessories/keymap/menu/W10shortcuts content + Setup-OpenText
script and cmd wrapper + version.txt) rides through the existing tar
pipe. Also added OpenText.exe to the legacy-cleanup rm list since the
old flat machineapps/OpenText.exe path is now obsolete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:10:35 -04:00
cproudlock
b4dd8a4197 VC++ extracted MSIs: bypass LaunchCondition CA via NOVSUI=1
The extracted VC++ 2008/2010/2012/2013/2022 MSIs have a hardcoded
CustomAction CA_LaunchCondition (type 19 = msidbCustomActionTypeError)
whose Target is "To install this product, please run Setup.exe.  For
other installation options, see the Installation section of ReadMe.htm."
The CA's Condition row in InstallExecuteSequence is:

  NOT( (ADDEPLOY = 1 OR NOVSUI = 1 OR VSEXTUI = 1 OR
        ADVERTISED = 1 OR ProductState >= 1) )

So it fires (= aborts the install with that error) unless one of those
sentinel properties is set on the command line. The 2008 MSI uses a
slightly different name (CA_LaunchCondition_5122) and a different set:

  NOT( (USING_EXUIH = 1 OR USING_EXUIH_SILENT = 1 OR ProductState >= 1) )

The bootstrappers normally set NOVSUI=1 / USING_EXUIH_SILENT=1 to
identify themselves as non-interactive installers. When we run the
extracted MSI directly via msiexec, the property isn't set, the CA
fires, msiexec returns 1603 with MSI Note 1: 1708, and the install
rolls back.

Fix: pass both properties unconditionally on every VC++ install. MSIs
ignore unknown properties, so one args string works for all of them.

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