diff --git a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 index 2cb31e5..31d57c3 100644 --- a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 +++ b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 @@ -309,29 +309,41 @@ function Get-Phase1 { } catch {} # Once Intune registration is fully landed (AAD-joined + Intune-enrolled - # + EnterpriseMgmt task present + baseline policies arrived), three - # things must happen together: - # 1. Delete INTERNETACCESS WiFi profile (gets bay off 172.16.x) - # 2. Connect AESFMA (gets bay onto corp 10.x via EAP-TLS - cert is - # already in LocalMachine\My thanks to Intune SCEP) - # 3. Push idx=7 to the PXE dashboard with the captured DeviceId so - # the dashboard card shows the QR for the Intune device id. - # All three fire in one shot per Monitor lifetime via cache flags. + # + EnterpriseMgmt task present + baseline policies arrived): + # - Push idx=7 to PXE dashboard with the DeviceId / QR. + # The INTERNETACCESS -> AESFMA WiFi swap is GATED SEPARATELY on the + # actual SCEP-provisioned machine cert landing in LocalMachine\My + # with Client Authentication EKU. Phase 1 essentials flip earlier + # than the cert delivery, and tearing INTERNETACCESS without the + # cert present leaves the bay with no path (AESFMA EAP-TLS would + # fail). Wait for the cert before swapping. $phase1Essential = ($script:cache.AzureAdJoined -and $script:cache.IntuneEnrolled -and $script:cache.EmTaskExists -and $policiesBaselineReady) - if ($phase1Essential -and -not $script:cache.InternetAccessDeleted) { + if (-not $script:cache.InternetAccessDeleted) { + # Look for any LocalMachine\My cert with Client Auth EKU + # (1.3.6.1.5.5.7.3.2). That's what AESFMA EAP-TLS uses. + $hasMachineClientAuthCert = $false try { - Write-Host "Intune registration complete - deleting INTERNETACCESS profile + reconnecting to AESFMA..." - $delOut = netsh wlan delete profile name="INTERNETACCESS" 2>&1 | Out-String - Write-Host $delOut - Start-Sleep -Seconds 2 - $conOut = netsh wlan connect name="AESFMA" ssid="AESFMA" 2>&1 | Out-String - Write-Host $conOut - $script:cache.InternetAccessDeleted = $true - } catch { - Write-Warning "WiFi swap (INTERNETACCESS -> AESFMA) failed: $_" + $clientAuthEku = '1.3.6.1.5.5.7.3.2' + $hasMachineClientAuthCert = [bool](Get-ChildItem 'Cert:\LocalMachine\My' -ErrorAction SilentlyContinue | + Where-Object { + $_.EnhancedKeyUsageList.ObjectId -contains $clientAuthEku + } | Select-Object -First 1) + } catch {} + if ($hasMachineClientAuthCert) { + try { + Write-Host "SCEP machine cert detected - swapping WiFi: delete INTERNETACCESS, connect AESFMA..." + $delOut = netsh wlan delete profile name="INTERNETACCESS" 2>&1 | Out-String + Write-Host $delOut + Start-Sleep -Seconds 2 + $conOut = netsh wlan connect name="AESFMA" ssid="AESFMA" 2>&1 | Out-String + Write-Host $conOut + $script:cache.InternetAccessDeleted = $true + } catch { + Write-Warning "WiFi swap (INTERNETACCESS -> AESFMA) failed: $_" + } } } if ($phase1Essential -and $script:cache.DeviceId -and -not $script:cache.DeviceIdReported) { @@ -814,13 +826,27 @@ function Format-Snapshot { # not just "arriving". Stops the category prompt firing pre-first-reboot # when only ~4 subkeys are present (we tested this empirically; clicking # "assign category" at 4 subkeys = imaging stalls + re-image required). + # AESFMA connected = bay has a live corp WLAN association on the + # machine-auth profile. That's the natural ground-truth that Phase 1 + # is operationally done (not just the data-side flags). Check the + # WLAN state via netsh wlan show interfaces - look for SSID=AESFMA + # with state=connected. + $aesfmaConnected = $false + try { + $wlanOut = netsh wlan show interfaces 2>$null + if ($wlanOut -match '(?ms)SSID\s*:\s*AESFMA.*?State\s*:\s*connected') { + $aesfmaConnected = $true + } + } catch {} $p1Done = ($Snap.Phase1.AzureAdJoined -and $Snap.Phase1.IntuneEnrolled -and - $Snap.Phase1.EmTaskExists -and $Snap.Phase1.PoliciesBaselineReady) + $Snap.Phase1.EmTaskExists -and $Snap.Phase1.PoliciesBaselineReady -and + $aesfmaConnected) $p1Status = Get-PhaseStatus @( @{ Ok = $Snap.Phase1.AzureAdJoined; Failed = $false }, @{ Ok = $Snap.Phase1.IntuneEnrolled; Failed = $false }, @{ Ok = $Snap.Phase1.EmTaskExists; Failed = $false }, - @{ Ok = $Snap.Phase1.PoliciesBaselineReady; Failed = $false } + @{ Ok = $Snap.Phase1.PoliciesBaselineReady; Failed = $false }, + @{ Ok = $aesfmaConnected; Failed = $false } ) # Phase 6 / Lockdown (shared by both flows, rendered last).