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>
This commit is contained in:
@@ -1,181 +1,76 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
REM sync_intune.bat - Thin launcher for Monitor-IntuneProgress.ps1
|
||||
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
|
||||
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.
|
||||
|
||||
setlocal
|
||||
title Intune Policy Sync
|
||||
|
||||
:: Self-elevate to administrator
|
||||
set "MONITOR=C:\Enrollment\shopfloor-setup\Shopfloor\Monitor-IntuneProgress.ps1"
|
||||
|
||||
REM Self-elevate to administrator
|
||||
net session >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
if errorlevel 1 (
|
||||
powershell -Command "Start-Process '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
:: Capture a carriage return character so the polling status line can overwrite
|
||||
:: itself in place (instead of scrolling the QR code off the top of the window).
|
||||
:: The copy /Z trick is the standard batch idiom for getting a literal CR.
|
||||
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Intune Policy Sync - %COMPUTERNAME%
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
:: Show Intune Device ID and QR code
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"$dsreg = dsregcmd /status 2>&1; "^
|
||||
"$line = $dsreg | Select-String DeviceId; "^
|
||||
"if ($line) { "^
|
||||
" $deviceId = $line.ToString().Split(':')[1].Trim(); "^
|
||||
" Write-Host \"Intune Device ID: $deviceId\" -ForegroundColor Cyan; "^
|
||||
" Write-Host ''; "^
|
||||
" $dllPath = 'C:\Enrollment\shopfloor-setup\Shopfloor\QRCoder.dll'; "^
|
||||
" if (Test-Path $dllPath) { "^
|
||||
" Add-Type -Path $dllPath; "^
|
||||
" $gen = New-Object QRCoder.QRCodeGenerator; "^
|
||||
" $data = $gen.CreateQrCode($deviceId, [QRCoder.QRCodeGenerator+ECCLevel]::L); "^
|
||||
" $ascii = New-Object QRCoder.AsciiQRCode($data); "^
|
||||
" $qr = $ascii.GetGraphic(1, [char]0x2588 + [char]0x2588, ' '); "^
|
||||
" Write-Host $qr; "^
|
||||
" } else { "^
|
||||
" Write-Host 'QRCoder.dll not found - skipping QR code' -ForegroundColor Yellow; "^
|
||||
" } "^
|
||||
"} else { "^
|
||||
" Write-Host 'Device not yet Azure AD joined.' -ForegroundColor Yellow; "^
|
||||
"}"
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Monitoring lockdown progress...
|
||||
echo ========================================
|
||||
echo Step 1: SFLD device configuration
|
||||
echo Step 2: DSC installation
|
||||
echo Step 3: SFLD - Consume Credentials task
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
:: ---- Polling cadence ----
|
||||
:: An Intune policy pull (Schedule #3 task) typically takes 30-90 seconds end-to-end.
|
||||
:: We poll every POLL_SECS but only RE-TRIGGER sync every RETRIGGER_POLLS iterations
|
||||
:: (so the previous sync has time to actually complete before we kick a new one).
|
||||
:: Old version called do_sync every 15s, which started a fresh sync before the prior
|
||||
:: one had finished and the Intune CSP engine treated each re-trigger as "start over",
|
||||
:: killing in-flight policy application work.
|
||||
set "POLL_SECS=30"
|
||||
set "RETRIGGER_POLLS=6"
|
||||
|
||||
:: ---- STEP 1: Wait for SFLD registry key ----
|
||||
echo [Step 1/3] Waiting for SFLD device configuration...
|
||||
echo Triggering initial Intune sync...
|
||||
call :do_sync
|
||||
set "poll_count=0"
|
||||
|
||||
:poll_sfld
|
||||
reg query "HKLM\Software\GE\SFLD" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
if not exist "%MONITOR%" (
|
||||
echo ERROR: Monitor not found at:
|
||||
echo %MONITOR%
|
||||
echo.
|
||||
goto sfld_done
|
||||
echo Was the shopfloor-setup tree staged correctly?
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
set /a poll_count+=1
|
||||
set /a remainder=!poll_count! %% !RETRIGGER_POLLS!
|
||||
if !remainder! equ 0 (
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%MONITOR%"
|
||||
set "MONITOR_EXIT=%errorlevel%"
|
||||
|
||||
echo.
|
||||
if "%MONITOR_EXIT%"=="0" (
|
||||
echo ========================================
|
||||
echo Setup complete - no reboot needed
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Still waiting after !poll_count! checks - re-triggering sync...
|
||||
call :do_sync
|
||||
)
|
||||
<nul set /p "=!CR! Check #!poll_count! - next check in !POLL_SECS!s... "
|
||||
timeout /t !POLL_SECS! /nobreak >nul
|
||||
goto poll_sfld
|
||||
|
||||
:sfld_done
|
||||
echo [DONE] SFLD device configuration received.
|
||||
|
||||
:: ---- STEP 2: Wait for DSC install completion ----
|
||||
echo.
|
||||
echo [Step 2/3] Waiting for DSC installation to complete...
|
||||
echo Triggering initial Intune sync...
|
||||
call :do_sync
|
||||
set "poll_count=0"
|
||||
|
||||
:poll_dsc
|
||||
set "dsc_ok=0"
|
||||
if exist "C:\LOGS\SFLD\DSCInstall.log" (
|
||||
findstr /C:"Installation completed successfully" "C:\LOGS\SFLD\DSCInstall.log" >nul 2>&1
|
||||
if !errorlevel! equ 0 set "dsc_ok=1"
|
||||
)
|
||||
if !dsc_ok! equ 1 (
|
||||
echo The post-reboot DSC install phase is finished. The device is ready.
|
||||
echo.
|
||||
goto dsc_done
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
set /a poll_count+=1
|
||||
set /a remainder=!poll_count! %% !RETRIGGER_POLLS!
|
||||
if !remainder! equ 0 (
|
||||
|
||||
if "%MONITOR_EXIT%"=="2" (
|
||||
echo ========================================
|
||||
echo REBOOT REQUIRED
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Still waiting after !poll_count! checks - re-triggering sync...
|
||||
call :do_sync
|
||||
)
|
||||
<nul set /p "=!CR! Check #!poll_count! - next check in !POLL_SECS!s... "
|
||||
timeout /t !POLL_SECS! /nobreak >nul
|
||||
goto poll_dsc
|
||||
|
||||
:dsc_done
|
||||
echo [DONE] DSC installation completed successfully.
|
||||
|
||||
:: ---- STEP 3: Wait for Consume Credentials scheduled task ----
|
||||
echo.
|
||||
echo [Step 3/3] Waiting for SFLD - Consume Credentials task...
|
||||
echo Triggering initial Intune sync...
|
||||
call :do_sync
|
||||
set "poll_count=0"
|
||||
|
||||
:poll_task
|
||||
schtasks /query /tn "SFLD - Consume Credentials" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
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.
|
||||
goto task_done
|
||||
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
|
||||
)
|
||||
set /a poll_count+=1
|
||||
set /a remainder=!poll_count! %% !RETRIGGER_POLLS!
|
||||
if !remainder! equ 0 (
|
||||
echo.
|
||||
echo Still waiting after !poll_count! checks - re-triggering sync...
|
||||
call :do_sync
|
||||
)
|
||||
<nul set /p "=!CR! Check #!poll_count! - next check in !POLL_SECS!s... "
|
||||
timeout /t !POLL_SECS! /nobreak >nul
|
||||
goto poll_task
|
||||
|
||||
:task_done
|
||||
echo [DONE] SFLD - Consume Credentials task found.
|
||||
|
||||
:: ---- COMPLETE ----
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Shopfloor Lockdown complete!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo All 3 steps passed:
|
||||
echo 1. SFLD device configuration
|
||||
echo 2. DSC installation
|
||||
echo 3. Consume Credentials task
|
||||
echo.
|
||||
echo A reboot is required to finalize.
|
||||
echo.
|
||||
choice /c YN /m "Reboot now"
|
||||
if !errorlevel! equ 1 shutdown /r /t 5
|
||||
exit /b
|
||||
|
||||
:: ---- Subroutine: trigger Intune sync ----
|
||||
:do_sync
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"$enrollPath = 'HKLM:\SOFTWARE\Microsoft\Enrollments'; "^
|
||||
"Get-ChildItem $enrollPath -ErrorAction SilentlyContinue | ForEach-Object { "^
|
||||
" $provider = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).ProviderID; "^
|
||||
" if ($provider -eq 'MS DM Server') { "^
|
||||
" $id = $_.PSChildName; "^
|
||||
" $taskPath = \"\Microsoft\Windows\EnterpriseMgmt\$id\\\"; "^
|
||||
" Get-ScheduledTask -TaskPath $taskPath -ErrorAction SilentlyContinue | "^
|
||||
" Where-Object { $_.TaskName -match 'Schedule #3' } | "^
|
||||
" ForEach-Object { Start-ScheduledTask -InputObject $_ }; "^
|
||||
" } "^
|
||||
"}" >nul 2>&1
|
||||
exit /b
|
||||
echo ERROR: Monitor exited with code %MONITOR_EXIT%
|
||||
pause
|
||||
exit /b %MONITOR_EXIT%
|
||||
|
||||
Reference in New Issue
Block a user