Single-site bay-stuck issue at WJ: GE Intune Report IP script filters
Get-NetIPAddress on StartsWith("10.") and posts everything matching
to the GE Tines webhook. Bays at WJ get the PXE LAN 10.9.100.x IP
captured and reported -> GE backend tags bays as on a non-corp 10.x
subnet -> dynamic group eligibility for SFLD policy never matches.
Other GE sites work because their PXE LANs aren't on 10.x at all.
Renumber PXE LAN to RFC1918 172.16.9.0/24 so the GE filter naturally
skips wired PXE addresses without any disable-NIC dance.
Server-side already in flight (netplan dual-bound, dnsmasq scope +
boot URL repointed, blancco preferences + grub.cfg + iPXE GetPxeScript
all sed'd to 172.16.9.1). This commit is the playbook / scripts /
docs side: 109 hits across 35 files sed'd in one shot.
After this lands + boot.wim is rebuilt + bays renumber off DHCP,
the 10.9.100.1 binding will be dropped from netplan as the final
cleanup step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
480 lines
23 KiB
PowerShell
480 lines
23 KiB
PowerShell
#Requires -RunAsAdministrator
|
|
<#
|
|
.SYNOPSIS
|
|
Captures endpoint state at three points in the imaging lifecycle so
|
|
they can be diffed pairwise.
|
|
|
|
.DESCRIPTION
|
|
Run at three checkpoints:
|
|
|
|
1) After PPKG enrollment, BEFORE the tech assigns a device category in
|
|
Intune (the device is enrolled but no category-driven policy/apps
|
|
have been delivered):
|
|
.\Capture-LockdownState.ps1 -Stage pre-category
|
|
|
|
2) After the device category is assigned and the device has synced
|
|
(apps + policies tied to that category have arrived/started, but
|
|
lockdown has not yet flipped the user from SupportUser to ShopFloor):
|
|
.\Capture-LockdownState.ps1 -Stage post-category
|
|
|
|
3) After lockdown DSC has fully landed (Winlogon DefaultUserName flipped,
|
|
AssignedAccess kiosk active, SFLD creds populated):
|
|
.\Capture-LockdownState.ps1 -Stage post-lockdown
|
|
|
|
Output lands under C:\ProgramData\state-<stage>-<timestamp>\.
|
|
Copy the whole folder back to the workstation
|
|
(\\172.16.9.1\image-upload or dump to pxe-images manually) and diff.
|
|
|
|
Diffing tips:
|
|
pre-category -> post-category : what the category-driven Intune
|
|
assignments brought in (the
|
|
SFLD-DSC Win32App + its registry
|
|
writes + scheduled tasks)
|
|
post-category -> post-lockdown : what the lockdown DSC profile
|
|
finished (kiosk shell, autologon
|
|
flip, AppLocker rules, etc.)
|
|
|
|
.PARAMETER Stage
|
|
One of 'pre-category', 'post-category', 'post-lockdown'. Controls the
|
|
output folder name. Legacy 'pre' / 'post' values are still accepted
|
|
and map to 'pre-category' / 'post-lockdown' respectively.
|
|
|
|
.EXAMPLE
|
|
.\Capture-LockdownState.ps1 -Stage pre-category
|
|
#>
|
|
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet('pre-category', 'post-category', 'post-lockdown', 'pre', 'post')]
|
|
[string]$Stage
|
|
)
|
|
|
|
# Map legacy values to new names
|
|
if ($Stage -eq 'pre') { $Stage = 'pre-category' }
|
|
if ($Stage -eq 'post') { $Stage = 'post-lockdown' }
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
|
$out = "C:\ProgramData\state-$Stage-$stamp"
|
|
New-Item -ItemType Directory -Path $out -Force | Out-Null
|
|
|
|
function Step($name, [scriptblock]$block) {
|
|
Write-Host "[$Stage] $name ..."
|
|
try { & $block } catch { Write-Warning " $name failed: $_" }
|
|
}
|
|
|
|
# --- MDM policy store: the subkeys that change are the clearest lockdown signal
|
|
Step "export PolicyManager registry" {
|
|
reg export 'HKLM\SOFTWARE\Microsoft\PolicyManager\current\device' "$out\PolicyManager.reg" /y | Out-Null
|
|
}
|
|
|
|
# --- MDM enrollment and provisioning sessions (category-targeted profiles land here)
|
|
Step "export Enrollments + Provisioning Sessions" {
|
|
reg export 'HKLM\SOFTWARE\Microsoft\Enrollments' "$out\Enrollments.reg" /y | Out-Null
|
|
reg export 'HKLM\SOFTWARE\Microsoft\Provisioning\Sessions' "$out\ProvisioningSessions.reg" /y 2>$null | Out-Null
|
|
reg export 'HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts' "$out\OMADMAccounts.reg" /y 2>$null | Out-Null
|
|
}
|
|
|
|
# --- GE-specific state (DSC, credentials, anything under GE\SFLD)
|
|
Step "export GE\SFLD registry" {
|
|
reg export 'HKLM\SOFTWARE\GE\SFLD' "$out\SFLD.reg" /y 2>$null | Out-Null
|
|
reg export 'HKLM\SOFTWARE\WOW6432Node\GE' "$out\GE-WOW6432.reg" /y 2>$null | Out-Null
|
|
}
|
|
|
|
# --- Intune Management Extension Win32App state (what apps are assigned + detection results)
|
|
Step "export IME Win32Apps registry" {
|
|
reg export 'HKLM\SOFTWARE\Microsoft\IntuneManagementExtension' "$out\IME.reg" /y 2>$null | Out-Null
|
|
}
|
|
|
|
# --- Shell / Winlogon (kiosk replaces Shell value here, autologon values, etc.)
|
|
Step "export Winlogon" {
|
|
reg export 'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' "$out\Winlogon.reg" /y | Out-Null
|
|
}
|
|
|
|
# --- AppLocker / software restriction (lockdown commonly adds rules here)
|
|
Step "export AppLocker + SRP" {
|
|
reg export 'HKLM\SOFTWARE\Policies\Microsoft\Windows\SrpV2' "$out\AppLocker.reg" /y 2>$null | Out-Null
|
|
reg export 'HKLM\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers' "$out\SRP.reg" /y 2>$null | Out-Null
|
|
}
|
|
|
|
# --- WDAC / Code Integrity policies
|
|
Step "export CI / WDAC" {
|
|
reg export 'HKLM\SYSTEM\CurrentControlSet\Control\CI' "$out\CI.reg" /y 2>$null | Out-Null
|
|
if (Test-Path 'C:\Windows\System32\CodeIntegrity\CiPolicies\Active') {
|
|
Copy-Item 'C:\Windows\System32\CodeIntegrity\CiPolicies\Active\*.cip' "$out\" -Force -EA SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# --- Shell Launcher / Assigned Access config (Win11 kiosk feature)
|
|
Step "export AssignedAccess + ShellLauncher" {
|
|
reg export 'HKLM\SOFTWARE\Microsoft\Windows\AssignedAccessConfiguration' "$out\AssignedAccess.reg" /y 2>$null | Out-Null
|
|
reg export 'HKLM\SOFTWARE\Microsoft\ShellLauncher' "$out\ShellLauncher.reg" /y 2>$null | Out-Null
|
|
}
|
|
|
|
# --- Local users and groups (kiosk accounts get created)
|
|
Step "snapshot local users and groups" {
|
|
try { Get-LocalUser | Select Name, Enabled, LastLogon, Description, SID |
|
|
Export-Csv "$out\LocalUsers.csv" -NoTypeInformation } catch {}
|
|
try { Get-LocalGroupMember Administrators -EA 0 | Select Name, PrincipalSource, ObjectClass |
|
|
Export-Csv "$out\Admins.csv" -NoTypeInformation } catch {}
|
|
}
|
|
|
|
# --- Scheduled tasks (lockdown often registers a monitor/re-enforcer task)
|
|
Step "snapshot scheduled tasks" {
|
|
Get-ScheduledTask | Select-Object TaskPath, TaskName, State,
|
|
@{n='Actions';e={($_.Actions | ForEach-Object { "$($_.Execute) $($_.Arguments)" }) -join '; '}},
|
|
@{n='Triggers';e={($_.Triggers | ForEach-Object { $_.CimClass.CimClassName }) -join ','}} |
|
|
Sort-Object TaskPath, TaskName |
|
|
Export-Csv "$out\Tasks.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- Services
|
|
Step "snapshot services" {
|
|
Get-Service | Select Name, DisplayName, Status, StartType |
|
|
Sort Name | Export-Csv "$out\Services.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- Installed apps (64 + 32 bit Uninstall hives)
|
|
Step "snapshot installed apps" {
|
|
@('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') |
|
|
ForEach-Object { Get-ItemProperty $_ -EA 0 } |
|
|
Where-Object DisplayName |
|
|
Select DisplayName, DisplayVersion, Publisher, InstallDate |
|
|
Sort DisplayName -Unique |
|
|
Export-Csv "$out\InstalledApps.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- Desktop contents (lockdown strips desktop icons on kiosk)
|
|
Step "snapshot desktops" {
|
|
Get-ChildItem -Path 'C:\Users\Public\Desktop' -ErrorAction SilentlyContinue |
|
|
Select Name, Length, LastWriteTime |
|
|
Export-Csv "$out\PublicDesktop.csv" -NoTypeInformation
|
|
Get-ChildItem -Path 'C:\Users\SupportUser\Desktop' -ErrorAction SilentlyContinue |
|
|
Select Name, Length, LastWriteTime |
|
|
Export-Csv "$out\SupportUserDesktop.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- Relevant logs
|
|
Step "copy Intune + Enrollment logs" {
|
|
$imeDir = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
|
|
$enrollDir = 'C:\Logs'
|
|
if (Test-Path $imeDir) {
|
|
New-Item "$out\IME-Logs" -ItemType Directory -Force | Out-Null
|
|
Copy-Item "$imeDir\*.log" "$out\IME-Logs\" -Force -EA SilentlyContinue
|
|
}
|
|
if (Test-Path $enrollDir) {
|
|
New-Item "$out\GE-Logs" -ItemType Directory -Force | Out-Null
|
|
Copy-Item "$enrollDir\*.log" "$out\GE-Logs\" -Recurse -Force -EA SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# --- dsregcmd status (AAD / Entra join state)
|
|
Step "dsregcmd /status" {
|
|
dsregcmd /status 2>&1 | Out-File "$out\dsregcmd.txt"
|
|
}
|
|
|
|
# --- Intune readiness probe (4-layer gate, frozen at this snapshot moment).
|
|
# Aligned with Microsoft's documented IME / ESP gates - see:
|
|
# https://learn.microsoft.com/en-us/windows/client-management/mdm-diagnose-enrollment
|
|
# https://patchmypc.com/blog/intune-management-extension-esp-phases/
|
|
#
|
|
# Layer 1 - AAD + MDM enrollment object exists
|
|
# AzureAdJoined, IntuneEnrolled (HKLM\Enrollments\<GUID> EnrollmentState=1)
|
|
# Layer 2 - Microsoft's own success markers
|
|
# MdmEnrollEvent75Found ("Auto MDM Enroll: Succeeded" in DeviceManagement-Enterprise-Diagnostics-Provider/Admin)
|
|
# No MdmEnrollEvent76Found (failure) within last 7d
|
|
# HasProvisioningCompleted=1 in OMADM\Accounts\<id>
|
|
# FirstSync IsSyncDone=1 in Enrollments\<id>\FirstSync
|
|
# Layer 3 - Policy actually delivered (CSP succeeded, not just provider registered)
|
|
# PolicyManager\Providers\<EnrollmentId>\default\Device exists
|
|
# PolicyManager\current\device has subkeys
|
|
# Layer 4 - IME running (Win32App / PowerShell channel ready)
|
|
# IME service running, IME log dir populated
|
|
# Sidecar Policy Provider InstallationState=Completed (best-effort log grep)
|
|
#
|
|
# Pre-category snapshots that show layers 2-3 red = category assigned too
|
|
# early -> reboot races the policy/payload pull -> stalled deploy -> re-image.
|
|
Step "intune category-readiness probe" {
|
|
$r = [ordered]@{
|
|
# Layer 1
|
|
AzureAdJoined = $false
|
|
AzureAdTenant = $null
|
|
DeviceId = $null
|
|
IntuneEnrolled = $false
|
|
EnrollmentId = $null
|
|
# Layer 2
|
|
MdmEnrollEvent75Found = $false
|
|
MdmEnrollEvent75Time = $null
|
|
MdmEnrollEvent76Found = $false
|
|
MdmEnrollEvent76Time = $null
|
|
HasProvisioningCompleted = $false
|
|
FirstSyncIsSyncDone = $false
|
|
# Layer 3
|
|
PolicyManagerProviderForEnrollment = $false
|
|
PolicyManagerCurrentDeviceSubkeys = 0
|
|
# Layer 4
|
|
ImeServiceRunning = $false
|
|
ImeLogDirPopulated = $false
|
|
SidecarInstallationState = $null # 'Completed' | 'InProgress' | $null
|
|
# Misc
|
|
LastSyncEventTime = $null
|
|
Stage = $Stage
|
|
SnapshotTime = $null
|
|
}
|
|
|
|
# Layer 1: AAD + tenant + DeviceId
|
|
try {
|
|
$ds = & dsregcmd /status 2>&1 | Out-String
|
|
if ($ds -match 'AzureAdJoined\s*:\s*YES') { $r.AzureAdJoined = $true }
|
|
if ($ds -match 'TenantId\s*:\s*(\S+)') { $r.AzureAdTenant = $matches[1] }
|
|
if ($ds -match '(?m)^\s*DeviceId\s*:\s*(\S+)') { $r.DeviceId = $matches[1] }
|
|
} catch {}
|
|
|
|
# Layer 1: enrollment record
|
|
try {
|
|
$eks = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Enrollments' -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$p = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
|
|
$p -and $p.EnrollmentState -eq 1 -and $_.PSChildName -ne 'Context'
|
|
}
|
|
if ($eks) {
|
|
$r.IntuneEnrolled = $true
|
|
$r.EnrollmentId = $eks[0].PSChildName
|
|
}
|
|
} catch {}
|
|
|
|
# Layer 2: event 75 (success) + 76 (failure) in last 7d
|
|
try {
|
|
$cutoff = (Get-Date).AddDays(-7)
|
|
$evts = Get-WinEvent -LogName 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin' -MaxEvents 1000 -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.TimeCreated -gt $cutoff }
|
|
$e75 = $evts | Where-Object { $_.Id -eq 75 } | Sort-Object TimeCreated -Descending | Select-Object -First 1
|
|
$e76 = $evts | Where-Object { $_.Id -eq 76 } | Sort-Object TimeCreated -Descending | Select-Object -First 1
|
|
if ($e75) { $r.MdmEnrollEvent75Found = $true; $r.MdmEnrollEvent75Time = $e75.TimeCreated.ToString('o') }
|
|
if ($e76) { $r.MdmEnrollEvent76Found = $true; $r.MdmEnrollEvent76Time = $e76.TimeCreated.ToString('o') }
|
|
$latest = $evts | Sort-Object TimeCreated -Descending | Select-Object -First 1
|
|
if ($latest) { $r.LastSyncEventTime = $latest.TimeCreated.ToString('o') }
|
|
} catch {}
|
|
|
|
# Layer 2 + 3: per-enrollment regs (only meaningful with an EnrollmentId)
|
|
if ($r.EnrollmentId) {
|
|
try {
|
|
$omadm = "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\$($r.EnrollmentId)"
|
|
if (Test-Path $omadm) {
|
|
$v = (Get-ItemProperty -Path $omadm -Name HasProvisioningCompleted -ErrorAction SilentlyContinue).HasProvisioningCompleted
|
|
if ($v -eq 1) { $r.HasProvisioningCompleted = $true }
|
|
}
|
|
} catch {}
|
|
try {
|
|
$fs = "HKLM:\SOFTWARE\Microsoft\Enrollments\$($r.EnrollmentId)\FirstSync"
|
|
if (Test-Path $fs) {
|
|
$v = (Get-ItemProperty -Path $fs -Name IsSyncDone -ErrorAction SilentlyContinue).IsSyncDone
|
|
if ($v -eq 1) { $r.FirstSyncIsSyncDone = $true }
|
|
}
|
|
} catch {}
|
|
try {
|
|
$pp = "HKLM:\SOFTWARE\Microsoft\PolicyManager\Providers\$($r.EnrollmentId)\default\Device"
|
|
if (Test-Path $pp) { $r.PolicyManagerProviderForEnrollment = $true }
|
|
} catch {}
|
|
}
|
|
|
|
# Layer 3: actual policy mirror under current\device
|
|
try {
|
|
$cur = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device' -ErrorAction SilentlyContinue
|
|
$r.PolicyManagerCurrentDeviceSubkeys = if ($cur) { $cur.Count } else { 0 }
|
|
} catch {}
|
|
|
|
# Layer 4: IME service + logs
|
|
try {
|
|
$svc = Get-Service -Name 'IntuneManagementExtension' -ErrorAction SilentlyContinue
|
|
if ($svc -and $svc.Status -eq 'Running') { $r.ImeServiceRunning = $true }
|
|
$imeLog = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
|
|
if ((Test-Path $imeLog) -and (Get-ChildItem $imeLog -ErrorAction SilentlyContinue | Select-Object -First 1)) {
|
|
$r.ImeLogDirPopulated = $true
|
|
}
|
|
} catch {}
|
|
|
|
# Layer 4: Sidecar Policy Provider InstallationState (best-effort grep of IME log)
|
|
try {
|
|
$imeLogFile = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log'
|
|
if (Test-Path $imeLogFile) {
|
|
$hits = Select-String -Path $imeLogFile -Pattern 'Sidecar.*?[Ii]nstallation\s*[Ss]tate.*?(Completed|InProgress|NotStarted|Failed)' -ErrorAction SilentlyContinue |
|
|
Select-Object -Last 1
|
|
if ($hits -and $hits.Matches.Count -gt 0 -and $hits.Matches[0].Groups.Count -ge 2) {
|
|
$r.SidecarInstallationState = $hits.Matches[0].Groups[1].Value
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# Verdict
|
|
$r.SnapshotTime = (Get-Date).ToString('o')
|
|
$r.ReadyForCategoryAssignment = (
|
|
$r.AzureAdJoined -and
|
|
$r.IntuneEnrolled -and
|
|
$r.MdmEnrollEvent75Found -and
|
|
(-not $r.MdmEnrollEvent76Found) -and
|
|
$r.HasProvisioningCompleted -and
|
|
$r.FirstSyncIsSyncDone -and
|
|
$r.PolicyManagerProviderForEnrollment -and
|
|
($r.PolicyManagerCurrentDeviceSubkeys -gt 0) -and
|
|
$r.ImeServiceRunning -and
|
|
$r.ImeLogDirPopulated
|
|
)
|
|
|
|
[pscustomobject]$r | ConvertTo-Json -Depth 3 | Out-File "$out\intune-readiness.json"
|
|
|
|
# Human-readable summary (PS 5.1-safe: $(if ...) subexpressions, no inline-if-as-expression)
|
|
@(
|
|
"Intune category-readiness probe @ $($r.SnapshotTime) (stage=$Stage)"
|
|
'------------------------------------------------------------------'
|
|
' Layer 1 - AAD + MDM enrollment object'
|
|
(" [{0}] AzureAdJoined tenant={1}" -f $(if ($r.AzureAdJoined) {'OK '} else {'FAIL'}), $r.AzureAdTenant)
|
|
(" [{0}] IntuneEnrolled id={1}" -f $(if ($r.IntuneEnrolled) {'OK '} else {'FAIL'}), $r.EnrollmentId)
|
|
' Layer 2 - Microsoft success markers'
|
|
(" [{0}] Event 75 (Auto MDM Enroll: Succeeded) time={1}" -f $(if ($r.MdmEnrollEvent75Found) {'OK '} else {'FAIL'}), $r.MdmEnrollEvent75Time)
|
|
(" [{0}] No event 76 in last 7d (no enroll failure) lastFail={1}" -f $(if ($r.MdmEnrollEvent76Found) {'FAIL'} else {'OK '}), $r.MdmEnrollEvent76Time)
|
|
(" [{0}] OMADM HasProvisioningCompleted=1" -f $(if ($r.HasProvisioningCompleted) {'OK '} else {'FAIL'}))
|
|
(" [{0}] Enrollments\<id>\FirstSync IsSyncDone=1" -f $(if ($r.FirstSyncIsSyncDone) {'OK '} else {'FAIL'}))
|
|
' Layer 3 - policy actually delivered'
|
|
(" [{0}] PolicyManager\Providers\<EnrollmentId>\default\Device exists" -f $(if ($r.PolicyManagerProviderForEnrollment) {'OK '} else {'FAIL'}))
|
|
(" [{0}] PolicyManager\current\device subkey count={1}" -f $(if ($r.PolicyManagerCurrentDeviceSubkeys -gt 0) {'OK '} else {'FAIL'}), $r.PolicyManagerCurrentDeviceSubkeys)
|
|
' Layer 4 - IME running (Win32App / PS1 channel)'
|
|
(" [{0}] IME service running" -f $(if ($r.ImeServiceRunning) {'OK '} else {'FAIL'}))
|
|
(" [{0}] IME log dir populated" -f $(if ($r.ImeLogDirPopulated) {'OK '} else {'FAIL'}))
|
|
(" [--] Sidecar InstallationState (best-effort) value={0}" -f $(if ($r.SidecarInstallationState) { $r.SidecarInstallationState } else { '(unknown)' }))
|
|
'------------------------------------------------------------------'
|
|
("ReadyForCategoryAssignment: $($r.ReadyForCategoryAssignment)")
|
|
("DeviceId: $($r.DeviceId)")
|
|
("LastMDMEvent: $($r.LastSyncEventTime)")
|
|
''
|
|
'Notes:'
|
|
' - All layers must be green before assigning device category in Intune.'
|
|
' - Even when ready locally, allow >=15 min after adding the user/device'
|
|
' to the target Entra group (server-side group eval delay, MS docs).'
|
|
' - Pre-category snapshot with red Layer 2/3 = root cause for "imaging'
|
|
' stalled and required re-image" (category raced the policy pull).'
|
|
) | Out-File "$out\intune-readiness.txt"
|
|
}
|
|
|
|
# --- SFLD credential manager YAML (identifies the Intune category)
|
|
Step "snapshot SFLD CredentialManager dir" {
|
|
$cmDir = 'C:\ProgramData\SFLD\CredentialManager'
|
|
if (Test-Path $cmDir) {
|
|
Get-ChildItem $cmDir -Recurse | Select FullName, Length, LastWriteTime |
|
|
Export-Csv "$out\SFLD-CredentialManager.csv" -NoTypeInformation
|
|
}
|
|
}
|
|
|
|
# --- SFLD DSC payload directory (where the DSC bootstrap drops scripts/creds)
|
|
Step "snapshot SFLD DSC dir" {
|
|
$dscDir = 'C:\ProgramData\SFLD\DSC'
|
|
if (Test-Path $dscDir) {
|
|
Get-ChildItem $dscDir -Recurse -Force | Select FullName, Length, LastWriteTime |
|
|
Export-Csv "$out\SFLD-DSC-Files.csv" -NoTypeInformation
|
|
}
|
|
}
|
|
|
|
# --- DSCDeployment.log (sits at C:\pc\, not under C:\Logs - written by the
|
|
# SFLD-DSC Win32App bootstrap. Critical for diagnosing version drift,
|
|
# missing sastoken.txt, fallback-path failures.)
|
|
Step "copy DSCDeployment + version artifacts" {
|
|
foreach ($p in 'C:\pc\DSCDeployment.log','C:\pc\version.txt') {
|
|
if (Test-Path $p) { Copy-Item $p "$out\" -Force -EA SilentlyContinue }
|
|
}
|
|
}
|
|
|
|
# --- IMECache file listing per Win32App content folder. Each subdir is
|
|
# <AppGUID>_<contentVersion>; the file list tells us which content
|
|
# payloads landed correctly (e.g. sastoken.txt presence/absence).
|
|
# Avoid copying .intunewin blobs (huge, encrypted) - just list.
|
|
Step "snapshot IMECache file listing" {
|
|
$imeContent = 'C:\Windows\IMECache'
|
|
if (Test-Path $imeContent) {
|
|
Get-ChildItem $imeContent -Recurse -File -Force -EA SilentlyContinue |
|
|
Select FullName, Length, LastWriteTime |
|
|
Export-Csv "$out\IMECache-Files.csv" -NoTypeInformation
|
|
}
|
|
}
|
|
|
|
# --- BPRT (PPKG bulk enrollment) runtime state. Setup-WCD.ps1 dumps
|
|
# packageInfo.json + criticalChecks.json here; their values prove
|
|
# whether PPKG parse + critical checks passed.
|
|
Step "copy BPRT runtime state" {
|
|
$bprtDir = 'C:\Logs\BPRT'
|
|
if (Test-Path $bprtDir) {
|
|
New-Item "$out\BPRT" -ItemType Directory -Force | Out-Null
|
|
Copy-Item "$bprtDir\*" "$out\BPRT\" -Recurse -Force -EA SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# --- C:\WCDApps state (where Setup-WCD.ps1 deploys helper scripts)
|
|
Step "snapshot WCDApps dir" {
|
|
$wcd = 'C:\WCDApps'
|
|
if (Test-Path $wcd) {
|
|
Get-ChildItem $wcd -Recurse -File -Force -EA SilentlyContinue |
|
|
Select FullName, Length, LastWriteTime |
|
|
Export-Csv "$out\WCDApps-Files.csv" -NoTypeInformation
|
|
}
|
|
}
|
|
|
|
# --- Windows Provisioning Diagnostics (Microsoft-side PPKG runtime logs)
|
|
Step "copy Windows Provisioning Diagnostics" {
|
|
$pdiag = 'C:\Windows\Provisioning\Diagnostics'
|
|
if (Test-Path $pdiag) {
|
|
New-Item "$out\Provisioning-Diagnostics" -ItemType Directory -Force | Out-Null
|
|
Copy-Item "$pdiag\*" "$out\Provisioning-Diagnostics\" -Recurse -Force -EA SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# --- Scheduled task last-run details (need LastRunTime + LastTaskResult, not just State)
|
|
Step "snapshot scheduled task run history" {
|
|
Get-ScheduledTask | ForEach-Object {
|
|
$info = $_ | Get-ScheduledTaskInfo -EA SilentlyContinue
|
|
[pscustomobject]@{
|
|
TaskPath = $_.TaskPath
|
|
TaskName = $_.TaskName
|
|
State = $_.State
|
|
LastRunTime = $info.LastRunTime
|
|
LastTaskResult = $info.LastTaskResult
|
|
NextRunTime = $info.NextRunTime
|
|
NumberOfMissedRuns = $info.NumberOfMissedRuns
|
|
}
|
|
} | Sort TaskPath, TaskName |
|
|
Export-Csv "$out\Tasks-RunHistory.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- DeviceManagement event log (outbound MDM sync errors - 429 throttles,
|
|
# AAD token failures, policy-pull stalls. Not visible in IME log alone.)
|
|
Step "export DeviceManagement event log" {
|
|
Get-WinEvent -LogName 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin' `
|
|
-MaxEvents 500 -EA SilentlyContinue |
|
|
Select TimeCreated, Id, LevelDisplayName, Message |
|
|
Export-Csv "$out\DeviceManagement-Events.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- Provisioning event log (PPKG application errors, runtime issues)
|
|
Step "export Provisioning event log" {
|
|
Get-WinEvent -LogName 'Microsoft-Windows-Provisioning-Diagnostics-Provider/Admin' `
|
|
-MaxEvents 500 -EA SilentlyContinue |
|
|
Select TimeCreated, Id, LevelDisplayName, Message |
|
|
Export-Csv "$out\Provisioning-Events.csv" -NoTypeInformation
|
|
}
|
|
|
|
# --- MDM device certificate (confirms enrollment is healthy at the cert layer)
|
|
Step "snapshot MDM certificates" {
|
|
Get-ChildItem Cert:\LocalMachine\My -EA SilentlyContinue |
|
|
Where-Object { $_.Subject -match 'MS-Organization|MS-Device|Microsoft Intune' -or $_.Issuer -match 'Microsoft Intune' } |
|
|
Select Subject, Issuer, Thumbprint, NotBefore, NotAfter |
|
|
Export-Csv "$out\MDM-Certificates.csv" -NoTypeInformation
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Snapshot complete: $out"
|
|
Write-Host ""
|
|
Write-Host "Next: copy that folder to the workstation. Easiest:"
|
|
Write-Host " net use Z: \\172.16.9.1\image-upload /user:pxe-upload pxe /persistent:no"
|
|
Write-Host " robocopy `"$out`" `"Z:\state-$Stage-$stamp`" /E /NFL /NDL /NJH /NJS"
|
|
Write-Host " net use Z: /delete /y"
|