Commit Graph

67 Commits

Author SHA1 Message Date
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
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
61e0f3a033 Preinstall: extract MSIs for all VC++ redists to suppress reboot
The Microsoft VC++ bootstrappers (vcredist*_x86.exe) ignore /norestart
and trigger immediate Windows reboots when CRT DLLs are in use, which
in practice is always. We saw this break a live Standard PC imaging
run (installlog showed the manual shutdown -a sequence between runs).

Fix follows the existing 2008 pattern: extract the inner MSIs from
each Burn bundle, run them via msiexec with REBOOT=ReallySuppress
(a hard Windows Installer property the bootstrapper can't override),
and treat exit 3010 as success. Files are now staged per-version
under dependencies/vcredist/<version>/ because each MSI's Media table
hardcodes its CAB filename, so the pairs would otherwise collide.

preinstall.json: 4 EXE entries replaced with 8 MSI entries (Min+Add
for 2012/2013/2022 because each version's Burn bundle ships them
as separate MSIs). 2008 also moved into the same vcredist/2008/
subdir for consistency. ProductCodes verified against the existing
detection paths (the previous "bootstrapper" GUIDs were actually
the Min runtime GUIDs inherited up the chain).

sync-preinstall.sh: now tarballs the dependencies/vcredist/ subtree
to preserve directory structure across the scp+sudo-cp boundary,
flat installers (UDC, Oracle) still copied individually, and the
remote install script now removes the legacy flat vc_red.msi/cab
plus the obsolete vcredist*_x86.exe bootstrappers on every sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:01:23 -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
cproudlock
163e58ab0b Fix dnsmasq reboot cron: use /etc/cron.d/ instead of crontab
Ansible cron module writes to root's crontab which requires cron
daemon running. Drop file in /etc/cron.d/ instead for reliability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:03:09 -04:00
cproudlock
b7cd0974f1 Blancco 7.15.1 upgrade: native kernel boot, BMC cloud licensing
- Switch to Blancco native kernel (vmlinuz-bde-linux) for hardware compat
- Config.img preferences with BMC connection (classic.eu-west-1.blancco.cloud)
- Disable wired LAN in preferences so WiFi takes default route to BMC
- WiFi SSID INTERNETACCESS configured in plaintext in config.img
- Slim GRUB EFI (1.3MB standalone with minimal modules)
- Fix Windows line endings in blancco-init.sh
- Add extra NIC drivers to switch_root initramfs
- SSH enabled in modified airootfs.sfs (root:blancco)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:54:25 -04:00
cproudlock
76165495ff Shopfloor PC type system, webapp enhancements, slim Blancco GRUB
- Shopfloor PC type menu (CMM, WaxAndTrace, Keyence, Genspect, Display, Standard)
- Baseline scripts: OpenText CSF, Start Menu shortcuts, network/WinRM, power/display
- Standard type: eDNC + MarkZebra with 64-bit path mirroring
- CMM type: Hexagon CLM Tools, PC-DMIS 2016/2019 R2
- Display sub-type: Lobby vs Dashboard
- Webapp: enrollment management, image config editor, UI refresh
- Upload-Image.ps1: robocopy MCL cache to PXE server
- Download-Drivers.ps1: Dell driver download pipeline
- Slim Blancco GRUB EFI (10MB -> 660KB) for old hardware compat
- Shopfloor display imaging guide docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:25:07 -04:00
cproudlock
6d0e6ee284 BIOS check fix, parallel downloads, shopfloor hardening
- Fix check-bios.cmd: replace parenthesized if blocks with goto labels
  (cmd.exe fails silently with if/else on network-mapped drives)
- Move BIOS check files to winpeapps/_shared/BIOS for reliable SMB access
- Add network wait loop before BIOS check in startnet.cmd
- Show firmware status in WinPE menu header (BIOS_STATUS variable)
- Add BypassNRO registry key to skip OOBE network requirement
- Refactor download-drivers.py with --parallel N flag (ThreadPoolExecutor)
- Set SupportUser AutoLogonCount to 3 in shopfloor unattend
- Add shutdown -a at start + shutdown /r /t 10 at end of Run-ShopfloorSetup.ps1
- Switch download-drivers.py from wget to curl for reliable stall detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 11:02:36 -04:00
cproudlock
86660a0939 Remove UEFI HTTP Boot config from dnsmasq
Not needed since iPXE chains to grubx64.efi for Blancco boot.
Simplifies DHCP config and avoids interfering with other UEFI clients.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:58:33 -05:00
cproudlock
dd2fec5a41 Blancco PXE boot via Ubuntu kernel switch_root
Blancco's own kernel freezes on Dell Precision towers during PXE boot.
Workaround: boot Ubuntu kernel via GRUB chainload, download Blancco's
666MB squashfs rootfs + 132MB kernel modules over HTTP, mount overlay
filesystem, and switch_root into Blancco's userspace.

- Add blancco-init.sh: custom initramfs init script for switch_root approach
- Add blancco-preferences.xml: pre-configured with network share for reports
- Update playbook: build initramfs, deploy Ubuntu kernel/modules, config
- Update prepare-boot-tools.sh: add HTTP modules to GRUB EFI build
- Add UEFI HTTP Boot support to dnsmasq config
- iPXE menu chains to grubx64.efi (replaces sanboot of ISO)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:20:00 -05:00
cproudlock
15983854ed Add git-crypt encrypted MOK signing keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:47:44 -05:00
cproudlock
5d5f5ce267 Add git-crypt encryption for MOK signing keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:47:09 -05:00
cproudlock
5de807143b Fix WinPE SMB auth and timeout commands, restore autoinstall disk match
- Add /user:pxe-upload pxe credentials to all net use commands (share requires auth)
- Replace timeout with ping delays (timeout.exe not available in WinPE)
- Restore size: largest disk match in autoinstall (root cause was BIOS RST mode)
- Simplify autoinstall late-commands structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:32:39 -05:00
cproudlock
a4abd238de Harden cloud-init disable and rebuild ISO properly in build-usb
Autoinstall user-data now disables cloud-init in multiple stages
(late-commands + runcmd + systemd masks) to prevent post-install
hangs. Also disables networkd-wait-online for air-gapped networks.
build-usb.sh switched from in-place ISO patching to full extract
and rebuild with xorriso mkisofs for reliable UEFI boot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:53:26 -05:00
cproudlock
d3a8e63b36 Fully unattended USB install: patch GRUB and remove cloud-init
- build-usb.sh now patches the ISO's grub.cfg with xorriso to add
  'autoinstall' kernel param (skips confirmation prompt) and reduces
  GRUB timeout from 30s to 5s
- Disable and remove cloud-init on the installed system to prevent
  boot delays on air-gapped network
- Fix ISO size calculation to use patched ISO for CIDATA partition offset

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:05:11 -05:00
cproudlock
d482cf4d0c Skip autoinstall confirmation prompt and disable cloud-init post-install
Adds interactive-sections: [] to avoid the "Continue with autoinstall?" prompt,
and disables cloud-init on the installed system to prevent boot delays.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 20:53:28 -05:00
cproudlock
57a53381f2 Ensure Media.tag exists for all images after import and via cron
Webapp now creates Deploy/Control/Media.tag after every image import.
Cron updated to create (not just touch) Media.tag for any image
directory that has Deploy/Control/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:58:39 -05:00
cproudlock
0da52cb083 Auto-reboot after imaging, auto-download pip-wheels in build scripts
startnet.cmd now polls for PESetup.exe completion and reboots with a
15-second countdown. Build scripts (USB + Proxmox) auto-download pip
wheels if the pip-wheels/ directory is missing. Added mok-keys/ to
gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:56:14 -05:00