Public/ConvertTo-NSPEntraPairingQueue.ps1

function ConvertTo-NSPEntraPairingQueue {
    <#
    .SYNOPSIS
        Builds a review queue from candidate records, bound to one tenant and forest. Offline.
    .DESCRIPTION
        One pair per record, each undecided. Identity is held as the Graph object ID and the AD
        objectGUID and SID; UPNs and distinguished names are labels from the time of review. The
        queue holds no credentials or tokens, but it does hold personal data: keep it in local state.
    .EXAMPLE
        $queue = ConvertTo-NSPEntraPairingQueue -Candidate $records -TenantId $inventory.TenantId -Forest 'contoso.example' -Domain $inventory.Domains -SourceAnchor $anchor
    #>

    [CmdletBinding()]
    [OutputType('NSP.M365.UserPairing.Queue')]
    param(
        [Parameter(Mandatory)][AllowEmptyCollection()][object[]]$Candidate,
        [Parameter(Mandatory)][guid]$TenantId,
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Forest,
        [Parameter(Mandatory)][AllowEmptyCollection()][string[]]$Domain,
        $SourceAnchor
    )

    $linked = @(foreach ($record in $Candidate) {
            if ($record.Status -ne 'Linked') { continue }
            foreach ($item in $record.Candidates) { if ($item.Confidence -eq 'Linked') { [string]$item.ObjectGUID } }
        })

    $pairs = foreach ($record in $Candidate) {
        $snapshots = foreach ($item in $record.Candidates) {
            [pscustomobject]@{
                adObjectGuid = [string]$item.ObjectGUID
                adSid = [string]$item.ObjectSID
                samAccountName = [string]$item.SamAccountName
                userPrincipalName = [string]$item.UserPrincipalName
                displayName = [string]$item.DisplayName
                distinguishedName = [string]$item.DistinguishedName
                domain = [string]$item.DomainDNSRoot
                enabled = [bool]$item.Enabled
                passwordLastSetUtc = $item.PasswordLastSetUtc
                confidence = [string]$item.Confidence
                score = [int]$item.Score
                evidence = @($item.Evidence)
                desiredImmutableId = $item.DesiredImmutableId
            }
        }
        [pscustomobject]@{
            cloudObjectId = [string]$record.CloudObjectId
            cloudLastPasswordChangeUtc = $record.CloudLastPasswordChangeUtc
            roleReview = [string]$record.RoleReview
            cloudUpnAtReview = [string]$record.CloudUserPrincipalName
            cloudDisplayNameAtReview = [string]$record.CloudDisplayName
            cloudImmutableIdAtReview = [string]$record.CloudImmutableId
            cloudStateAtReview = [string]$record.CloudState
            status = [string]$record.Status
            candidates = @($snapshots)
            decision = 'undecided'
            adObjectGuid = $null
            adSid = $null
            adDistinguishedNameAtReview = $null
            adUserPrincipalNameAtReview = $null
            adEnabledAtReview = $null
            matchEvidence = @()
            desiredImmutableId = $null
            decidedAtUtc = $null
            decidedBy = $null
            note = $null
            history = @()
            executionState = 'pending'
            execution = $null
            revalidation = $null
        }
    }

    $anchorVerified = $SourceAnchor -and $SourceAnchor.Provenance -eq 'Verified' -and $SourceAnchor.Strategy -ne 'Unknown'
    [pscustomobject]@{
        PSTypeName = 'NSP.M365.UserPairing.Queue'
        schemaVersion = 1
        tenantId = [string]$TenantId
        forest = $Forest
        domains = @($Domain)
        sourceAnchor = [pscustomobject]@{
            strategy = if ($SourceAnchor) { [string]$SourceAnchor.Strategy } else { 'Unknown' }
            status = if ($anchorVerified) { 'verified' } else { 'unverified' }
            syncServer = if ($SourceAnchor) { [string]$SourceAnchor.ComputerName } else { $null }
            collectedAtUtc = if ($SourceAnchor) { [string]$SourceAnchor.CollectedAtUtc } else { $null }
        }
        createdAtUtc = [datetime]::UtcNow.ToString('o')
        revalidatedAtUtc = $null
        needsRevalidation = $false
        linkedAdObjectGuids = $linked
        pairs = @($pairs)
    }
}