diff --git a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 index 9423eb5..4326a4a 100644 --- a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 +++ b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 @@ -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 }