Public/Compare-UTCMConfiguration.ps1
|
function Compare-UTCMConfiguration { <# .SYNOPSIS Compares two UTCM snapshots or a baseline snapshot against the current tenant state. .DESCRIPTION Downloads configuration items from both snapshots, normalizes them via ConvertTo-NormalizedJson, and runs Compare-Object to produce a diff. Two parameter sets: ByTwoSnapshots — compare BaselineSnapshotId vs CompareSnapshotId. AgainstCurrent — derive resources from the baseline, create a temp current-state snapshot, and compare. .PARAMETER BaselineSnapshotId GUID of the baseline snapshot (mandatory in both parameter sets). .PARAMETER CompareSnapshotId GUID of the second snapshot to compare against (ByTwoSnapshots set only). .PARAMETER PollingIntervalSeconds Seconds between polls when creating a current-state snapshot. Default: 10. .OUTPUTS Array of diff objects with id, displayName, type, normalizedData, and SideIndicator. '=>' = added/changed in current. '<=' = missing/changed from baseline. .EXAMPLE Compare-UTCMConfiguration -BaselineSnapshotId $baseId -CompareSnapshotId $compId .EXAMPLE # Compare baseline to live tenant Compare-UTCMConfiguration -BaselineSnapshotId $baseId #> [CmdletBinding(DefaultParameterSetName='ByTwoSnapshots')] param( # Baseline is mandatory for both parameter sets [Parameter(Mandatory, ParameterSetName='ByTwoSnapshots')] [Parameter(Mandatory, ParameterSetName='AgainstCurrent')] [ValidateScript({ if (-not (Validate-Guid $_)) { throw "BaselineSnapshotId '$_' is not a valid GUID." } $true })] [string]$BaselineSnapshotId, # Only required when comparing two static snapshots [Parameter(Mandatory, ParameterSetName='ByTwoSnapshots')] [ValidateScript({ if (-not (Validate-Guid $_)) { throw "CompareSnapshotId '$_' is not a valid GUID." } $true })] [string]$CompareSnapshotId, # Only used for AgainstCurrent [Parameter(ParameterSetName='AgainstCurrent')] [ValidateRange(5,300)] [int]$PollingIntervalSeconds = 10 ) Ensure-GraphConnection # Fetch baseline snapshot (must include items for comparison) $baseline = Get-UTCMSnapshot -SnapshotId $BaselineSnapshotId -IncludeItems # Fetch comparison snapshot (either current-state temp or another fixed snapshot) if ($PSCmdlet.ParameterSetName -eq 'AgainstCurrent') { # Derive resources from the baseline so the temp snapshot covers the same scope $baselineResources = @($baseline.configurationItems | ForEach-Object { $_.type } | Select-Object -Unique) if (-not $baselineResources -or $baselineResources.Count -eq 0) { throw "Baseline snapshot has no configurationItems; cannot determine resources for current-state comparison." } $compare = Get-UTCMCurrentStateSnapshot -Resources $baselineResources -PollingIntervalSeconds $PollingIntervalSeconds } else { $compare = Get-UTCMSnapshot -SnapshotId $CompareSnapshotId -IncludeItems } # Normalize to stable comparison set (avoid slow JSON roundtrips on entire object) $baselineItems = $baseline.configurationItems | ForEach-Object { [pscustomobject]@{ id = $_.id displayName = $_.displayName type = $_.type normalizedData = ConvertTo-NormalizedJson -InputObject $_.data } } $compareItems = $compare.configurationItems | ForEach-Object { [pscustomobject]@{ id = $_.id displayName = $_.displayName type = $_.type normalizedData = ConvertTo-NormalizedJson -InputObject $_.data } } # Compare by stable keys + normalized payload $diff = Compare-Object -ReferenceObject $baselineItems -DifferenceObject $compareItems ` -Property id, displayName, type, normalizedData -PassThru return $diff } |