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>
This commit is contained in:
cproudlock
2026-04-15 16:01:52 -04:00
parent 6db170bf54
commit a6648c5a40

View File

@@ -29,10 +29,20 @@
# true, they don't go back to false)
#
# EXIT CODES (consumed by sync_intune.bat):
# 0 = all done, post-reboot install complete, no further action needed
# 2 = reboot required (deployment phase done, install phase pending)
# 0 = all done, lockdown landed, system rebooting to ShopFloor autologon
# 2 = PPKG-reboot required (DSC deployment phase done, install phase pending)
# 1 = error
#
# PHASE 6 (Lockdown) was added 2026-04-15 after a pre/post state capture
# showed that "DSCInstall.log complete" is NOT the true end-of-line. After
# DSC finishes, MDM/PolicyCSP continues delivering the kiosk baseline which
# ends with two observable machine-level signals:
# - HKLM Winlogon DefaultUserName flipped from SupportUser -> ShopFloor
# - Local 'Administrator' account renamed to 'SFLDAdmin'
# Once both land, the script writes sync-complete.txt, runs the tech-facing
# Configure-PC machine-number prompt, and issues shutdown /r. The next boot
# auto-logs in as ShopFloor, materializing its profile from Default User.
#
# DETECTION REFERENCES (decoded from a real run captured at /home/camp/pxe-images/Logs/):
# Phase A (pre-reboot, ~08:35-08:52):
# - enrollment.log ppkg + computer name
@@ -282,6 +292,22 @@ function Get-CustomScriptStatuses {
# 'in-progress' - pre-reboot done AND we already rebooted (just waiting for
# post-reboot DSCInstall.log to finish)
# ============================================================================
function Get-LockdownState {
# Machine-level signals that the kiosk/lockdown baseline has finished
# being applied. Both are HKLM/SAM changes pushed by MDM PolicyCSP after
# DSCInstall.log finishes, so they land independently of which user is
# currently logged in. See pre/post state diff 2026-04-15 for rationale.
$wl = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
$defUser = Read-RegValue $wl 'DefaultUserName'
$autoUser = ($defUser -eq 'ShopFloor')
$adminRenamed = [bool](Get-LocalUser -Name 'SFLDAdmin' -ErrorAction SilentlyContinue)
return @{
AutologonShopfloor = $autoUser
AdminRenamed = $adminRenamed
Complete = ($autoUser -and $adminRenamed)
}
}
function Test-RebootState {
$deployLog = 'C:\Logs\SFLD\DSCDeployment.log'
$installLog = 'C:\Logs\SFLD\DSCInstall.log'
@@ -338,7 +364,23 @@ function Get-Snapshot {
if ($task) { $consumeCredsTask = $true }
} catch {}
# Real completion signal: creds actually landed in HKLM. The scheduled
# task existing just means DSC scheduled it; this checks it RAN and
# populated the share creds that the logon enforcers depend on.
$credsPopulated = $false
$credsBase = 'HKLM:\SOFTWARE\GE\SFLD\Credentials'
if (Test-Path $credsBase) {
foreach ($entry in (Get-ChildItem -Path $credsBase -ErrorAction SilentlyContinue)) {
$p = Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue
if ($p -and $p.TargetHost -and $p.Username -and $p.Password) {
$credsPopulated = $true
break
}
}
}
$customScripts = Get-CustomScriptStatuses -DscInstallLog $installLog
$lockdown = Get-LockdownState
return [PSCustomObject]@{
Function = $function
@@ -357,8 +399,11 @@ function Get-Snapshot {
Phase4 = $customScripts
Phase5 = @{
ConsumeCredsTask = $consumeCredsTask
CredsPopulated = $credsPopulated
}
Phase6 = $lockdown
DscInstallComplete = $installComplete
LockdownComplete = $lockdown.Complete
}
}
@@ -487,6 +532,16 @@ function Format-Snapshot {
$lines += " $(Mk $Snap.Phase2.SfldRoot) SFLD reg key"
$lines += " $(Mk $Snap.Phase2.FunctionOk) Function set"
$lines += " $(Mk $Snap.Phase2.SasTokenOk) DSC SAS token configured"
# Phase-1-done-but-Phase-2-stuck is the classic "tech needs to go
# set device category in Intune portal" state. Surface it loud
# rather than leaving the user staring at empty checkboxes.
$phase1Done = ($Snap.Phase1.AzureAdJoined -and $Snap.Phase1.IntuneEnrolled)
$phase2Done = ($Snap.Phase2.SfldRoot -and $Snap.Phase2.FunctionOk -and $Snap.Phase2.SasTokenOk)
if ($phase1Done -and -not $phase2Done) {
$lines += " --> ACTION: assign device category in Intune portal"
$lines += " (main / cmm / displaypcs / waxtrace)"
}
$lines += ""
$lines += " Phase 3: DSC deployment + install"
$lines += " $(Mk $Snap.Phase3.DeployLogExists) DSCDeployment.log present"
@@ -518,8 +573,13 @@ function Format-Snapshot {
}
}
$lines += ""
$lines += " Phase 5: Final"
$lines += " $(Mk $Snap.Phase5.ConsumeCredsTask) SFLD - Consume Credentials task"
$lines += " Phase 5: SFLD credentials"
$lines += " $(Mk $Snap.Phase5.ConsumeCredsTask) Consume Credentials task scheduled"
$lines += " $(Mk $Snap.Phase5.CredsPopulated) Share creds present in HKLM"
$lines += ""
$lines += " Phase 6: Lockdown"
$lines += " $(Mk $Snap.Phase6.AutologonShopfloor) Winlogon autologon = ShopFloor"
$lines += " $(Mk $Snap.Phase6.AdminRenamed) Administrator renamed -> SFLDAdmin"
} else {
$lines += ""
$lines += " (DSC phases not applicable for $pcType)"
@@ -581,10 +641,11 @@ function Wait-ForAnyKey {
function Invoke-SetupComplete {
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host " Setup complete - no reboot needed" -ForegroundColor Green
Write-Host " Setup + lockdown complete" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "The post-reboot DSC install phase is finished. The device is ready."
Write-Host "DSC install is finished and the kiosk baseline has fully landed"
Write-Host "(Winlogon flipped to ShopFloor autologon, Administrator renamed)."
if ($AsTask) {
# Write completion marker so future logon-triggered runs exit
@@ -601,16 +662,26 @@ function Invoke-SetupComplete {
}
# Machine number prompt only (startup items are auto-applied by
# 06-OrganizeDesktop from the PC profile). Tech can re-open
# sync_intune.bat manually to see QR code for inventory.
# 06-OrganizeDesktop from the PC profile). Runs in SupportUser
# session while tech is still near the PC; skipped silently if
# UDC/eDNC already hold a real number (tech typed at PXE or
# restore-from-.reg already populated them).
if ($ConfigureScript -and (Test-Path -LiteralPath $ConfigureScript)) {
try { & $ConfigureScript -MachineNumberOnly } catch { Write-Warning "Configure-PC failed: $_" }
}
# Reboot so Winlogon's new DefaultUserName=ShopFloor kicks in -
# autologon only fires at the logon boundary. Next boot brings up
# a clean ShopFloor session; this task will fire again for that
# user, see the marker, and exit in <1s.
Write-Host ""
Write-Host "Rebooting in 10 seconds for ShopFloor autologon..." -ForegroundColor Yellow
& shutdown.exe /r /t 10
exit 0
}
if ($Unattended) {
Write-Host "(Unattended mode - exiting)"
Write-Host "(Unattended mode - exiting; reboot left to caller)"
exit 0
}
Wait-ForAnyKey
@@ -721,8 +792,17 @@ try {
Write-Host ""
Write-Host $qrText
# Final state: post-reboot install complete
if ($snap.DscInstallComplete) {
# Final state: every phase landed. The gate is intentionally strict
# because each piece is needed for the device to function:
# - DscInstallComplete: device-config.yaml apps + custom scripts ran
# - CredsPopulated: SFLD share creds in HKLM (Machine-Enforce,
# Acrobat-Enforce, CMM-Enforce all need these)
# - LockdownComplete: kiosk policy baseline + Winlogon flipped to
# ShopFloor autologon + admin renamed
# Display PCs skip this whole branch via $skipDsc above.
if ($snap.DscInstallComplete -and
$snap.Phase5.CredsPopulated -and
$snap.LockdownComplete) {
Invoke-SetupComplete
}