Public/Compare-IntuneExport.ps1

# Auto-generated by module/Build-Module.ps1 from en/scripts/Compare-IntuneExport.ps1
# Edit the source script, then re-run Build-Module.ps1. Do not edit this file directly.

function Compare-IntuneExport {
<#
.SYNOPSIS
    Offline drift comparison between two exports (per object, severity-ranked).

.DESCRIPTION
    Matches objects BY NAME within each family folder and reports, for every family:
      ADDED – present in the DIFFERENCE export, absent from the REFERENCE (severity: medium)
      REMOVED – present in the REFERENCE, absent from the DIFFERENCE (severity: medium)
      CHANGED – same name in both, but content differs after ignoring volatile fields (severity: high)
    Useful before/after a migration, or between two point-in-time exports. No Graph connection needed.

.PARAMETER Reference
    Baseline export folder (the "before").

.PARAMETER Difference
    Export folder to compare against the baseline (the "after").

.PARAMETER OutputJson
    Optional path to write the full comparison as JSON.

.EXAMPLE
    .\Compare-IntuneExport.ps1 -Reference .\Export_old -Difference .\Export_new -OutputJson .\drift.json
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory)][string]$Reference,
    [Parameter(Mandatory)][string]$Difference,
    [string]$OutputJson
)

$ErrorActionPreference = 'Stop'
foreach ($p in $Reference, $Difference) { if (-not (Test-Path -LiteralPath $p)) { throw "Folder not found: $p" } }
$refRoot = (Resolve-Path -LiteralPath $Reference).Path
$difRoot = (Resolve-Path -LiteralPath $Difference).Path

# Fields that always change and must be ignored when comparing content.
$Volatile = 'id','createdDateTime','lastModifiedDateTime','modifiedDateTime','version','settingCount','isAssigned','supportsScopeTags'

function Get-Name { param($o) if ($o.displayName) { [string]$o.displayName } elseif ($o.name) { [string]$o.name } else { $null } }
function Get-Canonical {
    param($o)
    $h = [ordered]@{}
    foreach ($p in ($o.PSObject.Properties.Name | Sort-Object)) {
        if ($Volatile -contains $p) { continue }
        $h[$p] = $o.$p
    }
    ($h | ConvertTo-Json -Depth 100 -Compress)
}
function Load-Family { param($root)
    $map = @{}
    foreach ($dir in (Get-ChildItem $root -Directory -ErrorAction SilentlyContinue)) {
        $fam = $dir.Name; $map[$fam] = @{}
        foreach ($f in (Get-ChildItem $dir.FullName -Filter *.json -File -ErrorAction SilentlyContinue)) {
            try { $o = Get-Content $f.FullName -Raw | ConvertFrom-Json } catch { continue }
            $n = Get-Name $o; if ($n) { $map[$fam][$n] = (Get-Canonical $o) }
        }
    }
    return $map
}

$ref = Load-Family $refRoot
$dif = Load-Family $difRoot
$families = @($ref.Keys + $dif.Keys | Sort-Object -Unique)
$rows = @()

Write-Host ""
Write-Host ("Compare REFERENCE = {0}" -f $refRoot) -ForegroundColor Cyan
Write-Host (" DIFFERENCE = {0}" -f $difRoot) -ForegroundColor Cyan
$tAdd=0;$tRem=0;$tChg=0
foreach ($fam in $families) {
    $r = if ($ref.ContainsKey($fam)) { $ref[$fam] } else { @{} }
    $d = if ($dif.ContainsKey($fam)) { $dif[$fam] } else { @{} }
    $added=@(); $removed=@(); $changed=@()
    foreach ($n in $d.Keys) { if (-not $r.ContainsKey($n)) { $added += $n } elseif ($r[$n] -ne $d[$n]) { $changed += $n } }
    foreach ($n in $r.Keys) { if (-not $d.ContainsKey($n)) { $removed += $n } }
    if ($added.Count + $removed.Count + $changed.Count -eq 0) { continue }
    $tAdd+=$added.Count; $tRem+=$removed.Count; $tChg+=$changed.Count
    Write-Host ("--- {0} : +{1} added, -{2} removed, ~{3} changed ---" -f $fam,$added.Count,$removed.Count,$changed.Count) -ForegroundColor White
    foreach ($n in $changed) { Write-Host (" [CHANGED] {0}" -f $n) -ForegroundColor Yellow;  $rows += [pscustomobject]@{ Family=$fam; Name=$n; Change='CHANGED'; Severity='high' } }
    foreach ($n in $added)   { Write-Host (" [ADDED] {0}" -f $n) -ForegroundColor Green;   $rows += [pscustomobject]@{ Family=$fam; Name=$n; Change='ADDED';   Severity='medium' } }
    foreach ($n in $removed) { Write-Host (" [REMOVED] {0}" -f $n) -ForegroundColor Red;     $rows += [pscustomobject]@{ Family=$fam; Name=$n; Change='REMOVED'; Severity='medium' } }
}

Write-Host ""
Write-Host ("TOTAL: {0} added, {1} removed, {2} changed" -f $tAdd,$tRem,$tChg) -ForegroundColor Magenta
if ($OutputJson) { ($rows | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $OutputJson -Encoding UTF8; Write-Host ("JSON: {0}" -f $OutputJson) -ForegroundColor Cyan }
return ([int]($rows.Count -gt 0))
}