Public/Start-NSPEntraPairingWizard.ps1

function Start-NSPEntraPairingWizard {
    <#
    .SYNOPSIS
        Discovers tenant/DC/forest, inventories users and opens the pairing review.
    .DESCRIPTION
        Uses an existing Graph connection with Preflight read scopes. Displays the discovered
        environment before collecting users. User writes and hard-match apply are separate commands; the suffix menu offers explicit confirmed forest additions.
        Optional address preparation is a preview through the AD specialist's API, after review.
    .PARAMETER ReadOnly
        Keep forest suffix changes unavailable. Pairing choices and address previews remain
        available, with decisions saved privately. User writes and hard match remain separate.
    .EXAMPLE
        Start-NSPEntraPairingWizard -SourceAnchor $anchor
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param([Parameter(Mandatory)]$SourceAnchor, [string]$Server, [string]$Path,
        [switch]$AssumeNoRoleEligibility, [switch]$ReadOnly)
    $environment = Get-NSPEntraPairingEnvironment -Server $Server
    Import-NSPToolkitModule -Name 'NSP.Console' -MinimumVersion '0.1.5'
    Write-NSPConsoleLine -Message "Tenant: $($environment.TenantName) [$($environment.TenantId)] DC: $($environment.Server) Forest: $($environment.Forest)"
    if (-not (Read-NSPConfirm -Prompt 'Read users from this tenant and forest?')) { return }
    if (-not $PSCmdlet.ShouldProcess('pairing inventory and review queue', 'Read and review')) { return }
    Assert-NSPEntraPairingSession -Purpose Preflight -TenantId $environment.TenantId
    $inventory = Get-NSPEntraPairingInventory -ExpectedTenantId $environment.TenantId -Server $environment.Server -Forest -ReadRoles -AssumeNoRoleEligibility:$AssumeNoRoleEligibility
    $inventory | Add-Member -NotePropertyName ForestName -NotePropertyValue $environment.Forest -Force
    $records = @(Get-NSPEntraUserPairCandidate -Inventory $inventory -SourceAnchor $SourceAnchor)
    $queue = ConvertTo-NSPEntraPairingQueue -Candidate $records -TenantId $environment.TenantId -Forest $environment.Forest -Domain $inventory.Domains -SourceAnchor $SourceAnchor
    $review = Start-NSPEntraUserPairing -Queue $queue -Server $environment.Server -Forest -Path $Path
    $plans = @()
    $suffixReviews = @()
    while ($review.ReviewCompleted) {
        $action = Read-NSPMenu -Prompt 'Optional AD address preparation' -Choices @(
            [pscustomobject]@{ Label = 'Finish with the pairing queue'; Value = 'finish' }
            [pscustomobject]@{ Label = $(if ($ReadOnly) { 'Review missing forest UPN suffixes (preview only)' } else { 'Review / explicitly add missing cloud domains as forest UPN suffixes' }); Value = 'suffixes' }
            [pscustomobject]@{ Label = 'Preview AD UPN, mail and proxy address changes'; Value = 'addresses' }
        )
        if ($null -eq $action -or $action -eq 'finish') { break }
        if ($action -eq 'suffixes') {
            $suffixReviews += Start-NSPEntraPairingUpnSuffixReview -ExpectedTenantId $queue.tenantId -ExpectedForest $queue.forest -Server $environment.Server -Apply:(-not $ReadOnly)
            continue
        }
        if ($action -eq 'addresses') {
            try { $domains = Read-NSPEntraAddressDomains -Queue $queue -Inventory $inventory -Environment $environment }
            catch { Write-NSPConsoleLine -Message $_.Exception.Message -Role Warning; continue }
            if ($null -eq $domains) { continue }
            $settings = Read-NSPEntraAddressPolicy
            if ($null -eq $settings) { continue }
            $plans = @(Get-NSPEntraPairingAddressPlan -Queue $queue -Inventory $inventory -OldDomain $domains.OldDomain -NewDomain $domains.NewDomain -Format $settings.Format -AddressPolicy $settings.Policy -CloudPrimaryConflictAction $settings.CloudPrimaryConflictAction)
            Show-NSPEntraAddressPlans -Plan $plans
            break
        }
    }
    ConvertTo-NSPEntraWizardRun -Environment $environment -Inventory $inventory -Queue $queue -Review $review -AddressPlan $plans -SuffixReview $suffixReviews -ReadOnly:$ReadOnly
}