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>
This commit is contained in:
cproudlock
2026-04-09 13:30:12 -04:00
parent cd00d6d2e1
commit c464f45f4f
3 changed files with 336 additions and 121 deletions

View File

@@ -1,81 +1,58 @@
@echo off
REM sync_intune.bat - Thin launcher for Monitor-IntuneProgress.ps1
REM sync_intune.bat - Launches Monitor-IntuneProgress.ps1 in an elevated
REM PowerShell window.
REM
REM All polling, status display, sync triggering, and reboot detection lives in
REM the PowerShell monitor. This .bat just handles:
REM 1. Self-elevate to admin
REM 2. Invoke the monitor (which renders QR + 5-phase status table)
REM 3. Branch on the monitor's exit code:
REM 0 = post-reboot install complete, "done" message and exit
REM 2 = pre-reboot deployment done, prompt for reboot
REM else = error, pause so the user can read it
REM This .bat exists only because Windows doesn't give .ps1 files a
REM "double-click to run as admin" verb. It finds the monitor script,
REM then calls Start-Process -Verb RunAs to open a NEW elevated
REM PowerShell console hosting it. The cmd window that ran this .bat
REM exits immediately afterwards - all UI (QR code, status table, reboot
REM prompt) lives in the separate PS window.
REM
REM The monitor lives at C:\Enrollment\shopfloor-setup\Shopfloor\Monitor-IntuneProgress.ps1.
REM This .bat gets copied to the user's desktop by Run-ShopfloorSetup.ps1, so
REM %~dp0 doesn't necessarily point at the shopfloor-setup tree - we use the
REM absolute path to find the monitor instead.
REM Monitor lookup order:
REM 1. %~dp0lib\Monitor-IntuneProgress.ps1
REM - repo layout / canonical on-disk layout (bat in Shopfloor/, .ps1 in Shopfloor/lib/)
REM 2. %~dp0Monitor-IntuneProgress.ps1
REM - both files dropped in same dir (quick-test on desktop)
REM 3. C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1
REM - dispatcher-dropped, if this .bat is being invoked from elsewhere
REM 4. C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1
REM - canonical enrollment staging copy
REM
REM -NoExit on the inner PowerShell keeps the new window open after the
REM script finishes so the user can read any final output or error before
REM the window closes.
REM
REM Goto-based dispatch - no nested if blocks, no literal parens in echo
REM lines. CMD parses "if (...)" blocks by counting parens and will silently
REM eat any "(" or ")" inside an echo, so keeping the flow flat avoids that
REM class of syntax bomb entirely.
setlocal
title Intune Policy Sync
title Intune Policy Sync Launcher
set "MONITOR=%~dp0lib\Monitor-IntuneProgress.ps1"
if exist "%MONITOR%" goto :launch
set "MONITOR=%~dp0Monitor-IntuneProgress.ps1"
if exist "%MONITOR%" goto :launch
set "MONITOR=C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1"
if exist "%MONITOR%" goto :launch
REM Monitor lives under lib\ to keep it OUT of the dispatcher's baseline scan.
REM Run-ShopfloorSetup.ps1 does Get-ChildItem -Filter "*.ps1" on the Shopfloor\
REM dir (non-recursive) and runs every script it finds - if Monitor-IntuneProgress
REM lived there, the dispatcher would invoke it as a baseline script and hang the
REM whole shopfloor setup forever (it's an infinite poll loop, never returns).
set "MONITOR=C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1"
if exist "%MONITOR%" goto :launch
REM Self-elevate to administrator
net session >nul 2>&1
if errorlevel 1 (
powershell -Command "Start-Process '%~f0' -Verb RunAs"
exit /b
)
if not exist "%MONITOR%" (
echo ERROR: Monitor not found at:
echo %MONITOR%
echo.
echo Was the shopfloor-setup tree staged correctly?
pause
exit /b 1
)
powershell -NoProfile -ExecutionPolicy Bypass -File "%MONITOR%"
set "MONITOR_EXIT=%errorlevel%"
echo ERROR: Monitor-IntuneProgress.ps1 not found in any of:
echo %~dp0lib\Monitor-IntuneProgress.ps1
echo %~dp0Monitor-IntuneProgress.ps1
echo C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1
echo C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1
echo.
if "%MONITOR_EXIT%"=="0" (
echo ========================================
echo Setup complete - no reboot needed
echo ========================================
echo.
echo The post-reboot DSC install phase is finished. The device is ready.
echo.
pause
exit /b 0
)
if "%MONITOR_EXIT%"=="2" (
echo ========================================
echo REBOOT REQUIRED
echo ========================================
echo.
echo The pre-reboot deployment phase is complete. You must reboot now to
echo start the post-reboot DSC install phase, which downloads device-config.yaml
echo and runs the per-app wrappers (Install-eDNC, Install-UDC, Install-VCRedists,
echo Install-OpenText, etc).
echo.
choice /c YN /m "Reboot now"
if errorlevel 2 (
echo Cancelled - reboot manually when ready.
pause
exit /b 0
)
shutdown /r /t 5
exit /b 0
)
echo ERROR: Monitor exited with code %MONITOR_EXIT%
pause
exit /b %MONITOR_EXIT%
exit /b 1
:launch
echo Launching: %MONITOR%
powershell -NoProfile -Command "Start-Process powershell.exe -Verb RunAs -ArgumentList '-NoProfile','-NoExit','-ExecutionPolicy','Bypass','-File','%MONITOR%'"
exit /b