Public/Start-SqlSpnManager.ps1

# =============================================================================
# Script : Start-SqlSpnManager.ps1
# Author : Keith Ramsey
# =============================================================================
# Change Log
# -----------------------------------------------------------------------------
# 2026-05-09 Keith Ramsey Phase 2 release polish - DR-202 standard header applied.
# 2026-05-21 Keith Ramsey DR-309 v1 surface narrowing: scenarioName array no
# longer includes MSDTC (matches the picker change in
# Get-SqlSpnScenarioChoice).
# =============================================================================
function Start-SqlSpnManager {
    <#
    .SYNOPSIS
        Interactive entry point: walks the operator through SPN registration end-to-end.
    .DESCRIPTION
        Composes the full pipeline:
            Get-SqlSpnAccount -> Get-SqlSpnInfrastructure -> New-SqlSpnPlan ->
            Test-SqlSpnPlan -> Invoke-SqlSpnExecutionEngine

        Selection of source (discovered local services vs. standard pool), target,
        and scenario is collected via prompts. The engine itself never prompts;
        only this wrapper does. Pester unit tests target the underlying functions
        directly without going through this wrapper.
    .PARAMETER AutoConfirm
        Skip the final Y/N execution prompt. Other prompts still run unless mocked.
    .EXAMPLE
        Start-SqlSpnManager
    .EXAMPLE
        Start-SqlSpnManager -AutoConfirm
    .OUTPUTS
        None. Side effects: SPN registrations and audit log entries.
    .NOTES
        Per DR-007 the public engine surface (New-SqlSpnPlan,
        Invoke-SqlSpnExecutionEngine) is fully parameterizable. Use those directly
        for unattended automation.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param([switch]$AutoConfirm)

    # Standard Pool resolver: hardcoded 10-name default per DR-106; optional
    # JSON override via $env:SQLSPN_STANDARD_POOL per DR-205.
    $standardPool = Resolve-SqlSpnStandardPool

    $sourceSelection = Get-SqlSpnSourceChoice

    if ($sourceSelection -eq 0) {
        $candidates = Get-SqlSpnDiscoveryEngine
        $selected   = Get-SqlSpnVisualAccountSelection -AccountList $candidates
        if ($null -eq $selected) { return }
        $samAccountName = $selected.AccountName
        $manualPort     = $selected.Port
    }
    else {
        $standardList = $standardPool.GetEnumerator() |
            Select-Object @{N='Role'; E={$_.Name}}, @{N='AccountName'; E={$_.Value}}
        $selected = Get-SqlSpnVisualAccountSelection -AccountList $standardList
        if ($null -eq $selected) { return }
        $samAccountName = $selected.AccountName
        $manualPort     = $null
    }

    $scenarioIndex = Get-SqlSpnScenarioChoice
    $scenarioName  = @('Standalone','FCI','AlwaysOn')[$scenarioIndex]
    $targetName    = Get-SqlSpnTargetName

    # DR-103: redirect virtual / built-in identities to the local computer object
    # before AD verification. FCI Engine has its own redirect (Resolve-SqlSpnFciCno)
    # inside New-SqlSpnPlan.
    $samAccountName = Resolve-SqlSpnVirtualAccount -AccountName $samAccountName

    $account = Get-SqlSpnAccount -SamAccountName $samAccountName
    if (-not $account) { return }

    $infraParams = @{ Scenario = $scenarioName; TargetName = $targetName }
    if ($manualPort) { $infraParams.ManualPort = $manualPort }
    $infra = Get-SqlSpnInfrastructure @infraParams

    $plan = New-SqlSpnPlan -VerifiedAccount $account -Infrastructure $infra -Role Engine
    if (-not $plan) { return }

    $validation = Test-SqlSpnPlan -SpnPlan $plan
    if ($validation.Status -ne 'Clear') {
        Write-Warning "Plan validation: $($validation.Status) - $($validation.Reason)"
        return
    }

    Write-Host "`nPROPOSED SPNs:" -ForegroundColor Yellow
    $plan.ProposedSpns | ForEach-Object { Write-Host " $_" }
    Write-Host "ACCOUNT: $($plan.AccountDn)"
    if ($plan.CrossForest) { Write-Host "CROSS-FOREST: -T $($plan.TargetDomain)" -ForegroundColor Cyan }

    $proceed = if ($AutoConfirm) { $true } else { Get-SqlSpnExecutionConfirmation }
    if ($proceed -and $PSCmdlet.ShouldProcess("$($plan.ProposedSpns.Count) SPN(s) on $($plan.AccountDn)", 'Register SPNs')) {
        $plan | Invoke-SqlSpnExecutionEngine
    }
}