#Requires -RunAsAdministrator <# .SYNOPSIS Diff two snapshot dirs from Capture-LockdownState.ps1 to surface deltas (what arrived between the two checkpoints). .DESCRIPTION Compares two state-* dirs (typically pre-category vs post-category, or post-category vs post-lockdown) along these axes: - intune-readiness.json (5 readiness signals, did they flip?) - dsregcmd.txt (AAD join state diff) - Reg dumps (.reg files): line-level diff - File inventories (*.csv): rows added/removed - Event log CSVs (DeviceManagement-Events, Tasks-RunHistory): new rows count Output: human-readable summary to console + a delta-.txt next to the second snapshot dir. .PARAMETER Before Path to the earlier snapshot dir (e.g. state-pre-category-...) .PARAMETER After Path to the later snapshot dir (e.g. state-post-category-...) .EXAMPLE .\Compare-LockdownStates.ps1 -Before C:\ProgramData\state-pre-category-20260504-180000 -After C:\ProgramData\state-post-category-20260504-181500 #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Before, [Parameter(Mandatory=$true)] [string]$After ) $ErrorActionPreference = 'Continue' if (-not (Test-Path $Before)) { throw "Before dir not found: $Before" } if (-not (Test-Path $After)) { throw "After dir not found: $After" } $out = Join-Path $After ("delta-vs-" + (Split-Path $Before -Leaf) + ".txt") $lines = New-Object System.Collections.Generic.List[string] function Add-Line { param([string]$s) $lines.Add($s); Write-Host $s } Add-Line "==========================================================" Add-Line "Snapshot delta" Add-Line " Before: $Before" Add-Line " After: $After" Add-Line "==========================================================" Add-Line "" # --- 1. Intune readiness signals delta --- Add-Line '--- Intune readiness signals (5-row gate) ---' $rb = $null; $ra = $null $rbPath = Join-Path $Before 'intune-readiness.json' $raPath = Join-Path $After 'intune-readiness.json' if ((Test-Path $rbPath) -and (Test-Path $raPath)) { $rb = Get-Content $rbPath -Raw | ConvertFrom-Json $ra = Get-Content $raPath -Raw | ConvertFrom-Json $signals = 'AzureAdJoined','IntuneEnrolled','MdmSyncRecent','ImeServiceRunning','ImeLogDirPopulated' foreach ($s in $signals) { $b = $rb.$s; $a = $ra.$s $tag = if ($b -eq $a) { ' same' } elseif ($a) { '+ flipped TRUE' } else { '- regressed FALSE' } Add-Line (" {0,-25} before={1,-5} after={2,-5} {3}" -f $s, $b, $a, $tag) } Add-Line (" {0,-25} before={1,-5} after={2,-5}" -f 'PolicyMgr providers', $rb.PolicyManagerProviders, $ra.PolicyManagerProviders) Add-Line (" ReadyForCategoryAssignment: before={0} after={1}" -f $rb.ReadyForCategoryAssignment, $ra.ReadyForCategoryAssignment) } else { Add-Line " (intune-readiness.json missing in one or both snapshots)" } Add-Line '' # --- 2. dsregcmd diff (key boolean lines) --- Add-Line '--- dsregcmd state diff ---' $dsBefore = Get-Content (Join-Path $Before 'dsregcmd.txt') -ErrorAction SilentlyContinue $dsAfter = Get-Content (Join-Path $After 'dsregcmd.txt') -ErrorAction SilentlyContinue foreach ($key in 'AzureAdJoined','EnterpriseJoined','DomainJoined','TenantId','TenantName','MdmUrl','MdmTouUrl','MdmComplianceUrl','SettingsUrl') { $b = ($dsBefore | Select-String -Pattern "^\s*$key\s*:" -SimpleMatch).Line | Select-Object -First 1 $a = ($dsAfter | Select-String -Pattern "^\s*$key\s*:" -SimpleMatch).Line | Select-Object -First 1 if ($b -ne $a) { Add-Line " CHANGED $key" Add-Line " before: $(if ($b) { $b } else { '(missing)' })" Add-Line " after : $(if ($a) { $a } else { '(missing)' })" } } Add-Line '' # --- 3. Registry .reg files - line-level diff (count lines added/removed) --- Add-Line '--- registry dump deltas (line counts) ---' $regsBefore = Get-ChildItem $Before -Filter '*.reg' -ErrorAction SilentlyContinue foreach ($rb in $regsBefore) { $rbName = $rb.Name $raPath = Join-Path $After $rbName if (-not (Test-Path $raPath)) { continue } $bLines = Get-Content $rb.FullName -ErrorAction SilentlyContinue $aLines = Get-Content $raPath -ErrorAction SilentlyContinue $added = (Compare-Object -ReferenceObject $bLines -DifferenceObject $aLines | Where-Object SideIndicator -eq '=>').Count $removed = (Compare-Object -ReferenceObject $bLines -DifferenceObject $aLines | Where-Object SideIndicator -eq '<=').Count if ($added -or $removed) { Add-Line (" {0,-30} +{1,-4} -{2}" -f $rbName, $added, $removed) } } Add-Line '' # --- 4. CSV inventories - row count delta --- Add-Line '--- file inventory deltas (CSV row counts) ---' $csvsBefore = Get-ChildItem $Before -Filter '*.csv' -ErrorAction SilentlyContinue foreach ($cb in $csvsBefore) { $cbName = $cb.Name $caPath = Join-Path $After $cbName if (-not (Test-Path $caPath)) { continue } try { $bRows = (Import-Csv $cb.FullName).Count $aRows = (Import-Csv $caPath).Count if ($bRows -ne $aRows) { Add-Line (" {0,-35} before={1,-5} after={2,-5} delta={3:+#;-#;0}" -f $cbName, $bRows, $aRows, ($aRows - $bRows)) } } catch {} } Add-Line '' # --- 5. Newly-present + newly-absent files in the snapshot --- Add-Line '--- snapshot directory contents delta ---' $bf = Get-ChildItem $Before -File -Recurse | ForEach-Object { $_.FullName.Substring($Before.Length) } $af = Get-ChildItem $After -File -Recurse | ForEach-Object { $_.FullName.Substring($After.Length) } $onlyA = Compare-Object -ReferenceObject $bf -DifferenceObject $af -PassThru | Where-Object { $af -contains $_ -and $bf -notcontains $_ } $onlyB = Compare-Object -ReferenceObject $bf -DifferenceObject $af -PassThru | Where-Object { $bf -contains $_ -and $af -notcontains $_ } if ($onlyA) { Add-Line " NEW in After:" $onlyA | ForEach-Object { Add-Line " + $_" } } if ($onlyB) { Add-Line " REMOVED in After:" $onlyB | ForEach-Object { Add-Line " - $_" } } Add-Line '' # Persist $lines | Out-File $out -Encoding utf8 Write-Host "" Write-Host "Delta written to: $out" -ForegroundColor Cyan