Public/Test-NSPEntraUserHardMatch.ps1

function Test-NSPEntraUserHardMatch {
    <#
    .SYNOPSIS
        Checks every selected pair of a queue against the live tenant. Read-only.
    .DESCRIPTION
        Refuses another tenant before reading anything. Each selected pair comes back Ready,
        AlreadySet, Linked or Blocked with its reasons. Nothing is written, including the queue.
 
        Blocked when: the queue is not freshly revalidated, the anchor is not Verified, the pair is
        stale, the cloud user is gone, synced, was synced, is a guest or holds a different immutable
        ID, an AD user or anchor maps to several cloud users, another cloud user or a soft-deleted
        user holds the anchor, the user holds or is eligible for a directory role, or the tenant
        blocks hard match.
 
        Revalidation has to be recent, -MaxRevalidationAgeMinutes, because it is what proves the AD
        side: this command reads only Entra.
 
        A failed eligibility read blocks. If the tenant has no licence for PIM, and so no eligible
        roles, say so with -AssumeNoRoleEligibility; the result records that you did.
 
        Needs a session from Connect-NSPEntraPairingGraph -Purpose Preflight.
    .EXAMPLE
        $check = Test-NSPEntraUserHardMatch -Queue $queue
        $check.Items | Group-Object Outcome
    #>

    [CmdletBinding()]
    [OutputType('NSP.M365.UserPairing.HardMatchPreflight')]
    param(
        [Parameter(Mandatory)]$Queue,
        [ValidateRange(1, 1440)][int]$MaxRevalidationAgeMinutes = 60,
        [switch]$AssumeNoRoleEligibility
    )

    Import-NSPM365Graph
    Assert-NSPEntraPairingTenant -ExpectedTenantId $Queue.tenantId | Out-Null
    Assert-NSPEntraPairingSession -Purpose Preflight -TenantId $Queue.tenantId

    $queueBlocks = @(Get-NSPEntraHardMatchQueueBlock -Queue $Queue -MaxRevalidationAgeMinutes $MaxRevalidationAgeMinutes)
    $tenantState = Get-NSPEntraHardMatchTenantState -AssumeNoRoleEligibility:$AssumeNoRoleEligibility

    $usersById = @{}
    $holders = @{}
    foreach ($user in @(Invoke-NSPGraphCollection -Uri 'users?$select=id,userPrincipalName,userType,onPremisesImmutableId,onPremisesSyncEnabled&$top=999')) {
        if ($null -eq $user) { continue }
        $id = [string]$user['id']
        $usersById[$id] = $user
        $anchor = [string]$user['onPremisesImmutableId']
        if ($anchor) {
            if (-not $holders.ContainsKey($anchor)) { $holders[$anchor] = @() }
            $holders[$anchor] += $id
        }
    }

    $items = foreach ($pair in @($Queue.pairs | Where-Object decision -eq 'selected')) {
        $id = [string]$pair.cloudObjectId
        $check = @{
            Pair = $pair
            Queue = $Queue
            QueueBlock = $queueBlocks
            TenantState = $tenantState
            CloudUser = $usersById[$id]
            ObjectIdentifier = $(if ($usersById.ContainsKey($id)) { Get-NSPEntraObjectIdentifierState -CloudObjectId $id } else { 'Unknown' })
        }
        $anchor = [string]$pair.desiredImmutableId
        if ($anchor -and $holders.ContainsKey($anchor)) { $check.OtherAnchorHolder = @($holders[$anchor]) }
        Test-NSPEntraHardMatchPair @check
    }

    [pscustomobject]@{
        PSTypeName = 'NSP.M365.UserPairing.HardMatchPreflight'
        TenantId = [string]$Queue.tenantId
        CheckedAtUtc = [datetime]::UtcNow.ToString('o')
        QueueBlocks = $queueBlocks
        TenantBlocks = @($tenantState.Blocks)
        Warnings = @($tenantState.Warnings)
        Items = @($items)
        TenantState = $tenantState
    }
}