Public/Get-NSPEntraPairingReport.ps1
|
function Get-NSPEntraPairingReport { <# .SYNOPSIS Sorts every pair of a queue into one reconciliation category. Offline. .DESCRIPTION Categories: ConfirmedLinked, ExistingLinked, AwaitingSync, NeedsCheck, Failed, Blocked, Ready, Skipped, Flagged, Deferred, NeedsReview, Ambiguous, NotFound, Undecided and Diagnostic. ConfirmedLinked needs a revalidation after the write: Sync-NSPEntraPairingQueue is what sets it. Blocked and Ready come from -Preflight, so without one a selected pair reads Selected. -CsvPath and -JsonPath save the rows. Both refuse a path inside a Git checkout, because the report holds personal data. .EXAMPLE Get-NSPEntraPairingReport -Queue $queue -Preflight $check | Group-Object Category .EXAMPLE Get-NSPEntraPairingReport -Queue $queue -CsvPath 'D:\Secure\pairing-report.csv' #> [CmdletBinding()] param( [Parameter(Mandatory)]$Queue, $Preflight, [string]$CsvPath, [string]$JsonPath ) if ($CsvPath) { Assert-NSPEntraPairingStatePath -Path $CsvPath } if ($JsonPath) { Assert-NSPEntraPairingStatePath -Path $JsonPath } $checks = @{} if ($Preflight) { foreach ($item in @($Preflight.Items)) { $checks[[string]$item.CloudObjectId] = $item } } $rows = foreach ($pair in @($Queue.pairs)) { $check = $checks[[string]$pair.cloudObjectId] $category = if ($pair.executionState -eq 'linked') { 'ConfirmedLinked' } elseif ($pair.status -eq 'Linked') { 'ExistingLinked' } elseif ($pair.executionState -eq 'applied') { 'AwaitingSync' } elseif ($pair.executionState -in @('writing', 'unconfirmed', 'different', 'unknown')) { 'NeedsCheck' } elseif ($pair.executionState -eq 'failed') { 'Failed' } elseif ($pair.decision -eq 'selected') { if ($null -eq $check) { 'Selected' } elseif ($check.Outcome -eq 'Blocked') { 'Blocked' } else { 'Ready' } } elseif ($pair.decision -eq 'skipped') { 'Skipped' } elseif ($pair.decision -eq 'flagged') { 'Flagged' } elseif ($pair.decision -eq 'deferred') { 'Deferred' } elseif ($pair.decision -eq 'needs-review') { 'NeedsReview' } elseif ($pair.status -in @('Ambiguous', 'OneToMany')) { 'Ambiguous' } elseif ($pair.status -eq 'NotFound') { 'NotFound' } elseif ($pair.status -eq 'Suggested') { 'Undecided' } else { 'Diagnostic' } [pscustomobject]@{ PSTypeName = 'NSP.M365.UserPairing.ReportRow' Category = $category CloudObjectId = $pair.cloudObjectId CloudUserPrincipalName = $pair.cloudUpnAtReview Status = $pair.status Decision = $pair.decision ExecutionState = $pair.executionState AdObjectGuid = $pair.adObjectGuid AdDistinguishedName = $pair.adDistinguishedNameAtReview Reasons = @(if ($null -ne $check) { $check.Blocks }) -join '; ' Note = $pair.note } } $rows = @($rows) $utf8 = New-Object Text.UTF8Encoding($false) if ($JsonPath) { [IO.File]::WriteAllText($JsonPath, (ConvertTo-Json -InputObject $rows -Depth 4), $utf8) } if ($CsvPath) { $text = 'Category', 'CloudObjectId', 'CloudUserPrincipalName', 'Status', 'Decision', 'ExecutionState', 'AdObjectGuid', 'AdDistinguishedName', 'Reasons', 'Note' $safe = foreach ($row in $rows) { $cells = [ordered]@{} foreach ($name in $text) { $cells[$name] = ConvertTo-NSPEntraPairingCsvCell $row.$name } [pscustomobject]$cells } [IO.File]::WriteAllText($CsvPath, ((@($safe) | ConvertTo-Csv -NoTypeInformation) -join "`r`n"), $utf8) } $rows } |