Commit Graph

73 Commits

Author SHA1 Message Date
cproudlock
842ef88ccb Monitor: gate WiFi swap on SCEP cert + Phase 1 done on AESFMA connected
Two related fixes for the WiFi handoff timing:

1. WiFi swap (delete INTERNETACCESS + connect AESFMA) was firing on
   Phase 1 essentials being green (AAD + Intune + EmTask + baseline
   policies >=5). That signal flips ~minutes BEFORE the Intune SCEP
   machine cert actually lands in LocalMachine\My. Without the cert,
   AESFMA EAP-TLS auth fails and the bay has no path at all (we just
   deleted INTERNETACCESS). Stuck.

   New gate: walk Cert:\LocalMachine\My for any cert with Client
   Authentication EKU (1.3.6.1.5.5.7.3.2). When that's present, SCEP
   has delivered, AESFMA EAP-TLS will succeed. Swap then fires safely.

2. Phase 1 row on the on-bay Monitor display now ALSO requires
   AESFMA to be actively connected (parsed from netsh wlan show
   interfaces: SSID=AESFMA + State=connected). Phase 1 stays IN
   PROGRESS until the bay is operationally on corp WLAN, not just
   data-side enrolled. Matches user request "not complete phase 1
   until AESFMA is ready".

idx=7 dashboard push still fires on the original Phase 1 essentials
gate so the QR appears as soon as Intune registers the device,
independent of AESFMA join timing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:04:09 -04:00
cproudlock
a17b3fae6a Retire wired-disable/re-enable dance now that PXE LAN is 172.16.9.0/24
GE Report IP filters Get-NetIPAddress on StartsWith("10.") and PXE LAN
addresses are now 172.16.9.x which the filter skips naturally. The
disable-then-re-enable workaround was only needed when PXE LAN was
10.9.100.x and bays leaked that IP to the GE webhook. With the renumber
that whole flow is dead weight.

Removed:
 - playbook/shopfloor-setup/Shopfloor/lib/Disable-WiredNics.ps1 (file)
 - Run-ShopfloorSetup: Disable-WiredNics call after PPKG returns
 - Run-ShopfloorSetup: "GE Re-enable Wired NICs" SYSTEM task registration
 - Monitor-IntuneProgress: reportIpLog-gated wired re-enable + idx=7 retry
 - Monitor-IntuneProgress: reportIpDone gate on Phase 1 done check

Side benefit: stages 2-6 dashboard pushes no longer go dark mid-flow
(used to die between idx=6 and idx=7 when wired was off). Phase 1 row
on the Monitor screen now flips COMPLETE on the natural AAD + Intune
+ EmTask + baseline-policies condition instead of waiting on the
Report IP log file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:45:54 -04:00
cproudlock
ce604adcda Renumber PXE LAN from 10.9.100.0/24 to 172.16.9.0/24
Single-site bay-stuck issue at WJ: GE Intune Report IP script filters
Get-NetIPAddress on StartsWith("10.") and posts everything matching
to the GE Tines webhook. Bays at WJ get the PXE LAN 10.9.100.x IP
captured and reported -> GE backend tags bays as on a non-corp 10.x
subnet -> dynamic group eligibility for SFLD policy never matches.
Other GE sites work because their PXE LANs aren't on 10.x at all.

Renumber PXE LAN to RFC1918 172.16.9.0/24 so the GE filter naturally
skips wired PXE addresses without any disable-NIC dance.

Server-side already in flight (netplan dual-bound, dnsmasq scope +
boot URL repointed, blancco preferences + grub.cfg + iPXE GetPxeScript
all sed'd to 172.16.9.1). This commit is the playbook / scripts /
docs side: 109 hits across 35 files sed'd in one shot.

After this lands + boot.wim is rebuilt + bays renumber off DHCP,
the 10.9.100.1 binding will be dropped from netplan as the final
cleanup step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:30:32 -04:00
cproudlock
c6b249f866 Monitor: idx=7 push fires on Phase 1 essentials complete
Pair with the INTERNETACCESS -> AESFMA WiFi-swap commit. Once
AAD-joined + IntuneEnrolled + EmTaskExists + baseline policies
all true AND DeviceId is captured, push idx=7 to PXE dashboard
with the DeviceId immediately - don't wait for the Report IP log
(which depends on AESFMA join + script timing).

Side note: the legacy wired-NIC re-enable + reportIpLog-gated
idx=7 push block earlier in Get-Phase1 still exists. Both paths
guard on $script:cache.DeviceIdReported so only one fires, but
that block is dead-ish under the new WiFi-swap flow (no wired
disable -> no NIC state file -> re-enable block no-ops; Report
IP log gate may still fire idx=7 if Phase 1 essentials haven't
all flipped yet but Report IP did). Worth cleaning up next pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:24:18 -04:00
cproudlock
f404cd2892 Monitor: drop INTERNETACCESS WiFi + connect AESFMA on Phase 1 complete
When Intune registration lands (AAD-joined + IntuneEnrolled + EnterpriseMgmt
task present + baseline policies >=5), the bay is presumed to have its
SCEP-provisioned machine cert in LocalMachine\My. At that point the
INTERNETACCESS profile (172.16.x guest/internet WiFi) is no longer
useful - it just keeps the bay on a non-corp range so Report IP can't
find a 10.x to POST and the SFLD assignment filter never matches.

Action: in Get-Phase1, once all four registration signals are green,
fire 'netsh wlan delete profile name=INTERNETACCESS' then immediately
'netsh wlan connect name=AESFMA ssid=AESFMA'. Bay drops onto corp WLAN
with EAP-TLS, picks up a 10.x lease, Report IP fires cleanly. One-shot
per Monitor lifetime via $script:cache.InternetAccessDeleted flag.

This is the alternative to pre-staging the AESFMA profile during
imaging (which was reverted). AESFMA profile is assumed to exist
already because Intune's WiFi config profile delivers it during the
same enrollment that just completed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:22:40 -04:00
cproudlock
a80bdd6923 Filtered Report IP shim - POSTs only WJ corp ranges to GE webhook
GE's Intune-deployed ReportIPAddresses_v2.ps1 filters Get-NetIPAddress
with $_.StartsWith("10.") - too broad for WJ where PXE LAN is 10.9.x.
Can't modify the GE script (signed). Workaround: run our own POST to
the same Tines webhook with a tight subnet filter, beating GE's
script to the punch.

New Invoke-FilteredReportIP.ps1 (lib/):
 - Walks Get-NetIPAddress -AddressFamily IPv4
 - Filters strictly to 10.134.48.0/23 OR 10.48.249.0/26 (WJ corp)
 - POSTs to https://tines.apps.geaerospace.com/webhook/.../... with
   {host, fqdn, IP, force_update} body matching GE's payload shape
 - Local dedup via C:\ProgramData\GEA\FilteredReportIP\last-ip.txt
 - 6 retries with 10s backoff on transient HTTP error
 - Logs to C:\Logs\FilteredReportIP.log

Monitor-IntuneProgress main loop calls it each tick until it
succeeds once. After success, $filteredReportIpSucceeded flag short-
circuits further attempts.

If WJ later moves to a different VLAN, edit the $allowedRanges array
in Invoke-FilteredReportIP.ps1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:16:39 -04:00
cproudlock
86c7ffccd5 Monitor: bump post-reEnable settle 1s->5s + retry idx=7 push
Field test: bay imaged end-to-end, Monitor saw Report IP log,
captured DeviceId (Phase 1 went COMPLETE on screen + QR rendered
from dsregcmd), but the idx=7 push to the PXE dashboard never
landed before the Intune-triggered LAPS-prompt reboot. Root cause:
Enable-NetAdapter + 1s sleep doesn't give Windows time to renew
DHCP + populate routes before Send-PxeStatus POSTs to PXE webapp.
Push silently caught (Send-PxeStatus has try/catch), next tick was
30s away, LAPS reboot fired in between.

Two changes:
1. Sleep bumped 1s -> 5s after Enable-NetAdapter so wired path is
   actually carrying traffic before we POST.
2. When the tick that did the re-enable is also the push tick,
   retry Send-PxeStatus up to 6 times with 2s spacing (~12s total)
   instead of one-shot-then-give-up. Surfaces the warning to the
   transcript if all attempts fail so we can diagnose next time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:33:11 -04:00
cproudlock
59dbd64e37 Fix Report IP glob (.LOG not .txt) + add device-id copy button
Field bay surfaced two bugs in one diag dump (mdm-diag-F907T5X3 -
6PPSF24):

1. GE Proactive Remediation Report IP actually writes
   GE_Report_IP_Address_2_5.LOG (uppercase .LOG), not the .txt I
   assumed. Globs in two places had .txt filter -> never matched ->
   Phase 1 stuck IN PROGRESS forever even after the file landed and
   wired-NIC re-enable never fired. Drop extension from both globs
   in Monitor-IntuneProgress.ps1 (id=7 push gate + p1Done check).

2. The "GE Re-enable Wired NICs" SYSTEM task registered by
   Run-ShopfloorSetup was polling Autologon_Remediation.log for
   "Autologon set for ShopFloor" - a lockdown-time signal. Re-enable
   needs to fire at Report-IP time (well before lockdown) so that
   Monitor can push idx=7 with the QR before the Intune-triggered
   LAPS-prompt reboot. Repoint the SYSTEM task's poll to
   C:\Logs\GE_Report_IP_Address* (any extension).

Plus minor UX: copy button next to the Intune device ID on
/imaging dashboard so techs can grab the GUID without having to
double-click-select the <code>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:27:26 -04:00
cproudlock
7e1ea03f02 Decouple wired-NIC re-enable from DeviceId capture
Previous logic bundled re-enable into the idx=7 DeviceId-push gate.
If DeviceId hadn't been captured yet (AAD join lag, dsregcmd parse
miss), re-enable never fired even though the Report IP log was
already sitting at C:\Logs\GE_Report_IP_Address*.txt and the NIC
state file was on disk.

Split into two independent checks per tick:
 1. Re-enable: triggered by (Report IP log) AND (NIC state file) only.
 2. idx=7 push: still gated on (DeviceId) AND (Report IP log).

Fixes case observed in field: file exists in C:\Logs but wired NICs
stayed off and the bay couldn't reach the PXE dashboard for idx=7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:15:04 -04:00
cproudlock
2bfb2522c7 Phase 1 stays "in progress" until Report IP log appears
Monitor on-screen Phase 1 row used to show COMPLETE the instant AAD
join + Intune enroll + EmTask + baseline policies (>=15 subkeys) all
hit. That's misleading: the bay isn't actually registration-clean
until GE's Proactive Remediation Report IP script has fired on
WiFi-only and dropped C:\Logs\GE_Report_IP_Address*.txt. Without
that log, the SFLD ConfigurationProfile assignment filter still sees
a leaked 10.9.100.x IP and Phase 2 won't unblock.

Add reportIpDone to both the p1Done gate and the Get-PhaseStatus
input list so the on-screen Intune Registration row stays IN PROGRESS
until the file lands. Matches the dashboard side: idx=7 push is
already gated on the same file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:11:36 -04:00
cproudlock
b8328171eb Kill wired NICs post-stage-2 until Report IP log appears
Recurring Phase 2 "Device Configuration" stuck: GE Intune Proactive
Remediation "Report IP" script enumerates Get-NetIPAddress and POSTs
all IPs to a GE webhook. Bays cabled to air-gapped PXE LAN have
10.9.100.x leak into that report. GE backend tags bays "not on corp
net" -> dynamic-group assignment-filter at GE excludes them from the
SFLD ConfigurationProfile (Function + SasToken OMA-URI) ->
HKLM:\SOFTWARE\GE\SFLD\DSC never populates -> Monitor Phase 2 gate
never closes. Confirmed via mdm-diag-F907T5X3 dump: every Microsoft
policy delivered fine, zero SFLD/GE-namespace OMA-URI present.

Fix flow:
1. Run-ShopfloorSetup line 43: disable every Up wired NIC right after
   stage 2 push. NIC names persisted to
   C:\Enrollment\disabled-wired-nics.txt for later re-enable.
2. Stages 3-6 status pushes fail silently while wired is down (PXE
   server lives on the air-gapped 10.9.100.0/24 LAN, unreachable from
   WiFi). Dashboard goes dark in that window.
3. PPKG installs, immediate reboot, AAD/Intune enroll over WiFi only.
4. IME boots, Report IP script fires with corp-WiFi IP only, writes
   C:\Logs\GE_Report_IP_Address*.txt. Webhook records clean IP. GE
   dynamic group eligibility flips. SFLD policy delivers next sync.
5. Monitor-IntuneProgress detects the log file, re-enables every NIC
   in the persisted list, sleeps 1s for link, then pushes idx=7 with
   DeviceId so the dashboard card flips to QR before the Intune-
   triggered LAPS-prompt reboot lands.

Phase 1 remains "in progress" on the dashboard until Report IP fires
- correct, the bay isn't actually registration-clean until then.

Files:
- Disable-WiredNics.ps1 (new) - persists names + disables
- Run-ShopfloorSetup.ps1 - call after stage 2 Report-Stage
- Monitor-IntuneProgress.ps1 - gate idx=7 push + re-enable

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:22:41 -04:00
cproudlock
b5a067bd48 Cut Post-PPKG settle from 180s to 60s
Empirical evidence: MDM baseline policy push lands well within 60s
after PPKG triggers immediate reboot path on bays where assignment
filter matches. Bays where it doesn't deliver in 60s aren't going
to deliver in 180s either - they're blocked on an assignment-filter
or dynamic-group lag (sometimes 30+ min in GCC-High), not on the
raw sync window. Trimming 120s of dead wait off every imaging cycle.

Aggressive 30s Schedule #3 hammer + early-exit on baseline (>=5
subkeys) preserved - those still help bays that DO deliver fast.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:10:50 -04:00
cproudlock
44bbd23e4d Monitor-IntuneProgress: auto-fire idx=8 on lockdown detection
Local-user shopfloor fleet (ShopFloor is a LOCAL account) means
AzureAdPrt stays NO, user-scoped Intune policies never deliver, and
the natural completion gate (baseline policies >= 5 + DSCInstall
success + Phase 4 wrappers) never closes. Dashboard sessions stuck
at 7/8 / 87.5% forever even though the bay is functionally complete.

Real-world definition of "done" for these bays is lockdown applied.
Add a per-tick check in Get-Phase1 (alongside the DeviceId push) that
detects either:
  * Winlogon DefaultUserName -like 'ShopFloor*' AND AutoAdminLogon=1
  * OR C:\Enrollment\force-lockdown-applied.txt marker file

When either is present and not yet pushed, fire Send-PxeStatus
StageIndex=8 / StageTotal=8 / Status='succeeded' with the captured
IntuneDeviceId for the dashboard QR. One-shot per session via the
LockdownCompletePushed cache flag.

No need for the operator to run mark-complete.bat anymore -
Monitor's main loop will fire idx=8 within ~5s of force-lockdown
or autologon flip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:47:23 -04:00
cproudlock
a8d38f6117 imaging: load Send-PxeStatus at script scope + bump QR size to 160px
Monitor-IntuneProgress.ps1: the previous Ensure-SendPxeStatus function
ran '. $lib' from inside the function body. PowerShell's dot-source-
inside-function semantics put the imported Send-PxeStatus into the
function's LOCAL scope, not the script scope. By the time Get-Phase1
called Get-Command Send-PxeStatus, the function had already returned
and Send-PxeStatus was out of scope - silently never invoked, no log
entry at all (success or failure). Diagnostic confirmed: bay had
DeviceId in dsregcmd, manual Send-PxeStatus from operator prompt
fired idx=7 cleanly with QR rendered, but Monitor's automatic call
never showed up in C:\Logs\send-pxe-status.log.

Fix: dot-source at script top-level (outside any function). Then
Send-PxeStatus is in script scope where every function in the file
can call it. Keep Ensure-SendPxeStatus as a no-op stub for any caller
still invoking it.

imaging.html: bump QR data-qr-size from 56 to 160 px. A 36-char UUID
at ECC M needs ~29x29 modules; at 56px each module was ~1.5px which
is too tight for a phone camera to lock onto from typical distance.
160 px gives ~5 px/module which scans cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:41:52 -04:00
cproudlock
2e8cf4b5be Monitor-IntuneProgress: fix DeviceId capture gate
DeviceId capture was nested inside the -not AzureAdJoined gate. Once
AAD joined flipped true the block stopped running, but DeviceId only
appears in dsregcmd output AFTER AzureAdJoined is set, so the capture
never fired and Send-PxeStatus -IntuneDeviceId never pushed. Webapp
session JSON missing intune_device_id field; /imaging card couldn't
render the QR even though the bay-side Build-QRCodeText showed the
QR correctly (it calls dsregcmd each render with no gate).

Fix: change the gate condition so the dsregcmd call keeps running
while EITHER AzureAdJoined OR DeviceId is still missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:35:18 -04:00
cproudlock
1e21a54a41 imaging: idx=8 completion + Send-PxeStatus success+failure logging
Two related changes so the /imaging dashboard reaches 100% and so the
operator can see why POSTs are not arriving when a session stalls.

Monitor-IntuneProgress.ps1:
  * After sync-complete.txt is written (DSC + lockdown done) fire a
    final Send-PxeStatus -StageIndex 8 -StageTotal 8 -Status 'succeeded'
    + IntuneDeviceId. Previously the script exited without any final
    status push, so even a perfect run capped at idx=7 / 87.5%. The
    session now reaches 8/8 / 100% green when imaging actually finishes.

Send-PxeStatus.ps1:
  * Log EVERY POST attempt (both success and failure) to C:\Logs\
    send-pxe-status.log with idx, status, stage name, and either the
    HTTP code on success or the exception message on failure. Was
    previously silent-on-success, log-on-failure. Operator can now
    correlate dashboard state to actual outbound activity:
      OK  idx=2/8 status=in_progress http=200 stage='Run-ShopfloorSetup: starting'
      ERR idx=2/8 status=in_progress uri=http://10.9.100.1:9009/... err=Unable to connect
  * Errors still swallowed - imaging never blocks on a failed status push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:32:33 -04:00
cproudlock
9122b28c31 webapp: imaging progress dashboard + serial column on reports list
Adds end-to-end progress tracking for PXE imaging sessions and surfaces
each Blancco report's BIOS serial in the report list.

webapp:
  * services/imaging_status.py - JSON-per-serial state store under
    IMAGING_DIR (default /var/log/pxe-imaging). Atomic write via
    tempfile + rename. log_tail capped at 50 lines. Merges partial
    updates so clients can post just the current_stage tick.
  * config.py - new IMAGING_DIR env-overridable path.
  * services/csrf.py - explicit exempt list for machine-to-machine
    endpoints; /imaging/status is the first entry. Air-gapped LAN;
    trust-by-network for client posts.
  * app.py - four new routes:
      GET  /imaging               dashboard (renders all sessions)
      POST /imaging/status        client status push (JSON body)
      GET  /imaging/<serial>.json raw session JSON for ad-hoc polling
      POST /imaging/delete/<s>    clear a session from the dashboard
    Also parses each Blancco XML in the /reports list to surface
    system.serial + system.model columns.
  * templates/imaging.html - Bootstrap dashboard with per-session
    cards (state badge, progress bar, stage idx/total, mac, elapsed,
    log tail). meta http-equiv refresh=5 for auto-tick.
  * templates/base.html - new "Imaging Progress" nav entry.
  * templates/reports.html - Serial + Model columns added.

playbook:
  * shopfloor-setup/Shopfloor/lib/Send-PxeStatus.ps1 - new helper.
    Dot-source this then call Send-PxeStatus -Stage X -StageIndex N
    -StageTotal M from any stage script. BIOS serial via CIM, MAC via
    Get-NetAdapter, pctype + machinenumber from C:\Enrollment.
    Failures are swallowed to a local log so a network blip doesn't
    block imaging.
  * shopfloor-setup/Run-ShopfloorSetup.ps1 - dot-sources helper +
    posts at three coarse milestones (start, PPKG enrollment,
    handoff to Monitor-IntuneProgress).
  * shopfloor-setup/gea-shopfloor-keyence/09-Setup-Keyence.ps1 -
    posts at session start + after Install-FromManifest with
    succeeded/failed status derived from $rc. Other 09-Setup-*.ps1
    scripts can follow the same pattern.

ID is BIOS serial (stable across WinPE -> Windows transition and
across reboots, unlike hostname which is random pre-PPKG). Operator
already knows the serial of the bay they imaged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:07:18 -04:00
cproudlock
3896667c90 Set-MachineNumber: handle duplicate-PC reassignment (real -> real)
Tech catches a PC imaged with a wrong machine number. Previously the
share restore (NTLARS .reg + UDC settings + UDC live data) only fired
on the placeholder->real transition, so a real->real change rewrote
only UDC JSON, eDNC reg, and MTConnect Devices.xml - leaving the wrong
NTLARS config in place.

Update-MachineNumber.ps1: replace the placeholder-only guard with an
any-change guard so the share restore block fires on reassign too.
The existing one-shot migrated/ consumption keeps live-data restore
idempotent. Also writes C:\Enrollment\machine-number.txt to keep
imaging-time scripts in sync.

Set-MachineNumber.ps1 (both collections + nocollections): show a
confirmation dialog when reassigning between two real numbers, naming
old/new and listing what gets pulled. Audit each call to
C:\Logs\Shopfloor\reassign.log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:13:30 -04:00
cproudlock
5a0243dd9c 00-PreInstall-MachineApps: PCTypes filter alias-aware
Pairs with the rename reorg's other alias maps (Install-FromManifest,
GE-Enforce, Get-PCProfile, verify-state). Fleet PCs whose pc-type.txt
becomes a new gea-shopfloor-* string still match legacy preinstall.json
PCTypes filters like ["Standard"], ["CMM"]. Same map shape as the
others - extract to a shared lib later if drift becomes a problem.

Without this, a freshly-imaged PC writing pc-type.txt =
gea-shopfloor-collections would skip every preinstall.json entry whose
PCTypes is Standard/CMM/etc - imaging chain installs nothing past common
apps with PCTypes=*.

Deployed to PXE server at /srv/samba/enrollment/shopfloor-setup/Shopfloor/
2026-05-04 alongside the rest of the renamed shopfloor-setup tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 11:11:27 -04:00
cproudlock
ce3fbf5a28 sweep: pre-existing drift + matrix UDC entry + ignore 142MB EXE
Bundles drift left uncommitted from prior sessions and the UDC matrix
verify entry added today.

Drift items (all per session-progress.md, completed in earlier sessions
but never staged):

- playbook/check-bios.cmd (deleted, moved to BIOS/check-bios.cmd)
- playbook/migrate-to-wifi.ps1 (made no-op 2026-04-24 after the dnsmasq
  no-gateway fix removed the wired-NIC race that motivated it)
- playbook/preinstall/oracle/Install-Oracle11r2.cmd (post-OUI .ora copy
  added 2026-04-24)
- playbook/preinstall/oracle/tnsnames.ora (live tnsnames, 469 KB,
  deployed alongside the wrapper 2026-04-24)
- playbook/pxe_server_setup.yml (dnsmasq dhcp-option=3,6 commented,
  Oracle .ora deploy task added 2026-04-24)
- playbook/shopfloor-setup/BIOS/{check-bios.cmd, models.txt} (BIOS
  detection refinements)
- playbook/shopfloor-setup/Shopfloor/Force-Lockdown.bat
- playbook/shopfloor-setup/Shopfloor/Monitor-IntuneProgress.ps1
- playbook/shopfloor-setup/Shopfloor/SetShopfloorAutoLogon.bat (new)
- playbook/shopfloor-setup/Shopfloor/09-Install-PrinterInstallerMap.ps1
  (new, places PrinterInstallerMap.exe + Public Desktop shortcut at
  imaging time; manifest entry self-heals on tamper)
- playbook/shopfloor-setup/Shopfloor/lib/Show-IntuneDeviceQR.ps1 (new,
  standalone QR rendering for site that wanted just that piece)
- playbook/shopfloor-setup/gea-shopfloor-collections/{Install-eMxInfo.cmd.template,
  Restore-UDCData.ps1} (these were uncommitted in pre-rename Standard/;
  git mv didn't catch them because they were untracked at the time)
- docs/shopfloor-machine-imaging-guide.md (operator-facing how-to)

Matrix:
- common.test/matrix.json: add UDC verify entry to gea-shopfloor-collections
  row. Surfaces UDC silent-install issue (item H pending) instead of
  letting it pass silently.

.gitignore:
- PrinterInstallerMap.exe (142 MB) excluded. Track via LFS or stage on
  PXE server only - too big for regular git history. Untouched on disk
  so existing local copy still works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:49:43 -04:00
cproudlock
6dcf96e40a Phase 3+4 rename reorg: repo dir renames + startnet.cmd menu
Pairs with Phase 1+2 from earlier (alias maps in Install-FromManifest,
GE-Enforce, Get-PCProfile, verify-state). See project-shopfloor-rename-reorg
memory for the plan.

Phase 3 (repo + paths):
- git mv per-PC-type dirs to gea-shopfloor-* names:
    Standard      -> gea-shopfloor-collections
    CMM           -> gea-shopfloor-cmm
    Keyence       -> gea-shopfloor-keyence
    Genspect      -> gea-shopfloor-genspect
    WaxAndTrace   -> gea-shopfloor-waxtrace
    Display       -> gea-shopfloor-display
    Lab           -> gea-shopfloor-common (folded; Timeclock+Lab merge)
- New gea-shopfloor-nocollections/ (clone of collections sans UDC scripts).
- New gea-shopfloor-heattreat/ (placeholder, README only).
- Move Standard/ntlars-backups/ -> _ntlars-backups/ (per-MN, not per-type).
- Run-ShopfloorSetup.ps1: Resolve-PCTypeDir helper walks alias group when
  the on-disk dir for the current pcType is missing. Set-MachineNumber
  helper-copy gated on collections|nocollections|legacy Standard-Machine.
- Update-MachineNumber.ps1: pcProfiles lookups try gea-shopfloor-collections
  first, fall back to legacy Standard-Machine. PowerShell 5.1 compatible
  (no null-coalesce).

Phase 4 (startnet.cmd menu):
- Choice 3 "GEA Shopfloor" now drills into a 9-item sub-menu instead of
  going straight to enrollment. Sub-cats:
    1. Machine with Collections        -> gea-shopfloor-collections
    2. Machine without Collections     -> gea-shopfloor-nocollections
    3. Common (Timeclock, Lab)         -> gea-shopfloor-common
    4. Keyence                         -> gea-shopfloor-keyence
    5. CMM                             -> gea-shopfloor-cmm
    6. Genspect                        -> gea-shopfloor-genspect
    7. Heattreat                       -> gea-shopfloor-heattreat
    8. Wax and Trace                   -> gea-shopfloor-waxtrace
    9. Display                         -> gea-shopfloor-display
- Office menu (existing 6-option) follows for every sub-cat.
- Machine number prompt only for collections + nocollections.
- pc-subtype.txt + display-type.txt no longer written. PCTYPE is a
  single full string (gea-shopfloor-*); subtype-aware code paths fall
  back to empty and resolve via the alias map.
- CMM bootstrap stage gate switched from "%PCTYPE%"=="CMM" to
  "%PCTYPE%"=="gea-shopfloor-cmm".

Test harness:
- B-enforce/run.sh PCSUBTYPE default changed from "Machine" to "" so
  single-arg invocation matches the new single-string scheme. Two-arg
  legacy form ("Standard Machine") still works via aliasing.
- B-enforce/tamper.ps1 alias-aware Test-MatrixEntryMatches mirroring
  verify-state.ps1.

Smoke-tested on win11 VM as SYSTEM via qga: B-enforce harness 5-phase
cycle (stage / baseline / tamper / heal / idempotent) passes 10/10
with PCType=gea-shopfloor-collections AND with legacy "Standard Machine"
two-arg form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:09:16 -04:00
cproudlock
c890e5b46c test harness + Get-PCProfile: alias-aware lookups for rename reorg
Phase 5 + 6 of the gea-shopfloor-* rename.

Get-PCProfile.ps1: when the legacy profileKey ("Standard-Machine",
"CMM", etc.) is missing from siteConfig.pcProfiles, walks the alias
group and returns the first matching new key ("gea-shopfloor-collections",
"gea-shopfloor-cmm", etc.). Vice versa: a fleet PC writing the new
string finds its profile under the old key. Same alias map shape as
GE-Enforce + Install-FromManifest, kept in sync manually for now -
extract to shared file later if drift becomes a problem.

matrix.json: adds 3 new rows for gea-shopfloor-nocollections,
gea-shopfloor-common (Timeclock+Lab merge), gea-shopfloor-heattreat
(placeholder). Existing rows for legacy names retained; the new
verify-state alias resolution lets either be requested.

verify-state.ps1: Test-MatrixEntryMatches walks the alias map so
harness invocation with "Standard Machine" or "gea-shopfloor-collections"
both resolve to the same matrix row.

Smoke-tested via qga-as-SYSTEM on win11: legacy Standard/Machine,
new gea-shopfloor-collections, and new gea-shopfloor-nocollections
all return 10/10 pass against current VM state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:29:32 -04:00
cproudlock
75b85bfde6 Update-MachineNumber: pull per-bay udc_settings.json from SFLD on placeholder->real
When the tech transitions a 9999-placeholder PC to its real machine number,
also restore the per-bay udc_settings_<num>.json from
\\tsgwp00525\shared\spc\udc\settings_backups\. PXE-time preinstall can't reach
this share (no SFLD creds yet), so 00-PreInstall uses the local C:\Enrollment
mirror; post-config the share is reachable, so the renumber path goes direct
to the canonical source.

Adds udcSettingsSharePath to site-config.json under Standard-Machine.

Bundles in prior uncommitted work in the same file: ntlars reg restore,
UDC data restore (CurrentData.json + ArchivedData/), MTConnect Devices.xml
inline rewrite + service restart, and one-shot consume of per-bay UDC
backup -> migrated/<timestamp>/.
2026-04-30 12:34:53 -04:00
cproudlock
6e9053b83c 00-PreInstall: pre-stage udc_webserver_settings.json + firewall/NetFx3 hardening
Add staging block that copies udc_webserver_settings.json from the enrollment
share to C:\ProgramData\UDC during preinstall, mirroring the existing
udc_settings.json pattern. New PCs were imaging without UDC web server
config because the file was never wired into the imaging flow (only the
remote-maintenance task in powershell/remote-execution touched it).

Also folds in two prior uncommitted hardening blocks in the same script:
firewall NotifyOnListen=False (suppress Oracle OUI's listen-port prompt)
and NetFx3 pre-enable (Oracle 11.2's welcome path needs .NET 3.5).
2026-04-30 12:16:41 -04:00
cproudlock
6e85e19c85 S: drive mapping via HKLM\Run, autologon-count non-intervention, Phase 4 no-scripts handling
- Register-MapSfldShare.ps1: swap scheduled task for HKLM\Run entry. Task with -GroupId runs in session 0 with no HKCU, so /persistent:yes fails and the drive mapping isn't visible to Explorer. Run key fires at Explorer startup in the interactive user's session with full token + HKCU. Unregisters legacy 'GE Shopfloor Map S: Drive' task for PCs already imaged.
- Run-ShopfloorSetup.ps1: stop bumping AutoLogonCount (99 at start, 4 at end). Windows decrements per-logon and at 0 clears AutoAdminLogon + DefaultPassword, which nukes the lockdown-configured ShopFloor autologon. Re-enable-wired-NICs task now gates on Autologon_Remediation.log 'Autologon set for ShopFloor' instead of SFLD creds, so wired stays off through the whole Intune+DSC+lockdown chain.
- Monitor-IntuneProgress.ps1: Phase 4 treats 'no custom scripts' as COMPLETE when DSC install is done (was WAITING, which stalled the state machine on PC types without scripts). Push retrigger out to 15min when entering lockdown-wait so a stale 5min retrigger doesn't fire mid-Remediation. Removed the AutoLogonCount delete in Invoke-SetupComplete since we no longer set it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:42:22 -04:00
cproudlock
2ab6055125 Fix ShopFloor autologon persistence, S: drive mapping, sync throttle
AutoLogonCount depletion:
  Run-ShopfloorSetup set AutoLogonCount=4 for SupportUser. Windows
  decrements per-logon; at 0 it clears AutoAdminLogon + DefaultPassword,
  nuking the lockdown-configured ShopFloor autologon. Fix: delete
  AutoLogonCount in Invoke-SetupComplete before the lockdown reboot.
  ShopFloor's Autologon.exe-set config persists indefinitely.

Sync_intune window on ShopFloor:
  The marker-check path used 'exit 0' but the task runs with -NoExit,
  leaving a dangling PowerShell window on every ShopFloor logon. Fix:
  [Environment]::Exit(0) kills the host outright, defeating -NoExit.

S: drive mapping:
  Vendor ConsumeCredentials.ps1 calls New-StoredCredential -Persist
  LocalMachine (needs admin) before net use. ShopFloor is non-admin so
  cred-store fails silently and net use has no auth. Fix: new
  Map-SfldShare.ps1 reads HKLM creds and passes them inline to
  net use /user: -- no Credential Manager needed, works as Limited.
  Register-MapSfldShare updated to stage + reference our script.

Wired NIC re-enable:
  SYSTEM task polls for SFLD creds (Phase 5), re-enables wired NICs,
  self-deletes. Replaces the broken Enable-NetAdapter in Monitor
  (Limited principal can't enable NICs). No-WiFi devices unaffected
  (migrate-to-wifi never disables, re-enable is a no-op).

Sync throttle:
  15 min retrigger when only waiting for lockdown (was 5 min for all
  phases). Avoids interrupting the Intune Remediation script.

Defect Tracker path:
  All references corrected to C:\Program Files (x86)\WJF_Defect_Tracker.

QR code retry:
  Build-QRCodeText retried every poll cycle until DeviceId appears
  (was single-shot that could miss the dsregcmd timing window).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:29:02 -04:00
cproudlock
f73f999938 Unified Common-Enforce for cross-type apps, add WJF Defect Tracker
Replaces the Acrobat-only enforcer with a generic Common-Enforce that
handles all cross-PC-type apps from one manifest + one scheduled task
on the SFLD share at \\tsgwp00525\shared\dt\shopfloor\common\apps\.

Renames:
  Acrobat-Enforce.ps1        -> Common-Enforce.ps1
  Register-AcrobatEnforce    -> Register-CommonEnforce
  acrobat-manifest.json      -> common-apps-manifest.json
  common.acrobatSharePath    -> common.commonAppsSharePath
  'GE Acrobat Enforce' task  -> 'GE Common Apps Enforce' task
  C:\Program Files\GE\Acrobat -> C:\Program Files\GE\CommonApps

Register-CommonEnforce cleans up the legacy 'GE Acrobat Enforce' task
if present from a prior image.

WJF Defect Tracker (replaces ClickOnce):
  - Added to preinstall.json (PCTypes=*, fleet-wide imaging-time install)
  - MSI staged on PXE at pre-install/installers/
  - Added to common-apps-manifest with FileVersion detection on
    C:\Program Files\WJF_Defect_Tracker\Defect_Tracker.exe
  - site-config + 06-OrganizeDesktop: shortcut changed from ClickOnce
    'existing' to exe-path pointing at the MSI-installed binary
  - Update workflow: drop new MSI on share, bump DetectionValue

CMM 09-Setup-CMM: added goCMM + DODA to the ACL grant list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:13:05 -04:00
cproudlock
ac23759486 UDC firewall rules + Acrobat Reader as default PDF viewer
- Pre-create Windows Firewall inbound-allow rules for UDC.exe and
  MTConnect agent.exe before UDC_Setup.exe runs, suppressing the
  interactive "allow through firewall?" dialogs during silent install.

- Set Adobe Acrobat Reader (Acrobat.Document.DC) as the default .pdf
  handler via dism /import-defaultappassociations. Runs in
  03-ShellDefaults.ps1 so the OEMDefaultAssociations.xml is in place
  before ShopFloor's profile is created on first logon. Edge no longer
  claims .pdf on new profiles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:18:44 -04:00
cproudlock
85e74e5dd1 UDC settings: pre-stage from server backups, fix arg format, action prompts
Root cause found via decompiling UDC_Setup.exe: it never writes
udc_settings.json from CLI args. Instead it pulls
Settings_Backups\udc_settings_<num>.json from \\tsgwp00525\shared\SPC\UDC
-- which is unreachable at imaging time (no SFLD creds yet). Silent
File.Exists() false, settings never copy, UDC lands on Evendale defaults.

Fix: stage 80 udc_settings_*.json backups under
shopfloor-setup/Standard/udc-backups/ (same tree as ntlars-backups,
xcopy'd to C:\Enrollment\ by existing startnet.cmd). 00-PreInstall
pre-creates C:\ProgramData\UDC\udc_settings.json from the matching
backup BEFORE UDC_Setup.exe runs. Installer's server-side copy silently
fails (unreachable), our pre-staged file survives.

Also:
- preinstall.json UDC InstallArgs corrected: "West Jefferson" -9999
  (quoted spaced site + dash-prefixed number, confirmed via decompile)
- Update-MachineNumber.ps1 UDC.exe relaunch: quoted site + dash number
- Monitor-IntuneProgress: action prompts (Select Device Category after
  Phase 1; Initiate ARTS Lockdown after Phase 5/creds), Display flow
  (3-phase: Registration -> Config -> Lockdown), Phase 6 IME-based
  lockdown detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:44:34 -04:00
cproudlock
db55bd772a sync_intune: professional UI, IME-based lockdown detection
UI overhaul:
  Replaced the 30+ line checkbox-per-sub-item view with a clean
  6-line phase summary styled for GE Aerospace branding. Each phase
  shows one colored status tag: [COMPLETE] green, [IN PROGRESS] cyan,
  [WAITING] gray, [FAILED] red. Action hint for Phase 2 (device
  category assignment) in yellow. QR code + Device ID below.

Phase 6 lockdown detection:
  Replaced DefaultUserName + admin-rename checks (which pass at PPKG
  time, way too early) with Intune Remediation log artifacts:
  - Autologon_Remediation.log: "Autologon set for ShopFloor"
  - Autologon_Detection.log: "matches the expected value: 1"
  These only exist after the Intune Remediation cycle actually fires
  post-enrollment, making Phase 6 a true end-of-chain signal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:35:22 -04:00
cproudlock
a4de11814d Force-Lockdown.bat + S: drive logon mapper for ShopFloor end-user
Force-Lockdown.bat (SupportUser desktop):
  Vendor escape hatch when Intune Lockdown push hasn't applied within
  ~30 minutes. Self-elevates via UAC, prompts for typed YES confirmation
  that an ARTS request is in place, then runs sfld_autologon.ps1.

Register-MapSfldShare.ps1 (every PC type):
  The SFLD vendor's 'SFLD - Consume Credentials' scheduled task is
  principal-restricted (admin-only) so it fires for SupportUser logon
  but not for ShopFloor logon -- ShopFloor lands at the desktop with
  no S: drive and no way to reach \\tsgwp00525\shared. Workaround:
  register a parallel 'GE Shopfloor Map S: Drive' AtLogOn task with
  Principal=BUILTIN\Users + RunLevel=Limited that invokes the vendor's
  C:\ProgramData\SFLD\CredentialManager\ConsumeCredentials.ps1 in the
  interactive user's session. Vendor script handles cred-store + net use
  end to end; we just give it a wider trigger principal. Cross-PC-type
  because every shopfloor account needs S:.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:31:18 -04:00
cproudlock
a334a56f1e WiFi detection: widen regex to catch hyphen-less 'WiFi' + 802.11
Realtek RTL8852BE describes itself as 'Realtek RTL8852BE WiFi 6 802.11ax
PCIe Adapter' -- no hyphen in 'WiFi' -- which the previous regex
'Wi-Fi|Wireless' rejected. migrate-to-wifi.ps1's gate then exited 0
silently and neither wired NIC got disabled, leaving the imaging chain
running over PXE ethernet for the entire PPKG phase.

New regex Wi-?Fi|Wireless|WLAN|802\.11 covers:
  - Wi-Fi (Intel-style with hyphen)
  - WiFi (Realtek-style without hyphen)
  - Wireless (Intel Wireless-AC, Killer Wireless)
  - WLAN (some Realtek/MediaTek variants)
  - 802.11 (vendor-agnostic spec reference, fallback)

Applied in two callers:
- migrate-to-wifi.ps1 (3 occurrences: gate + disable + re-enable on timeout)
- Monitor-IntuneProgress.ps1 (re-enable wired on sync_intune startup)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:03:19 -04:00
cproudlock
c23b803dc6 sync_intune: align Phase 3/5/6 columns; ignore benign 'Failed: 0' tails
Cosmetic + accuracy fixes spotted on the live test PC:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:42:21 -04:00
cproudlock
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
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
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
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
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
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