Public/Start-SqlSpnConfiguration.ps1

# =============================================================================
# Script : Start-SqlSpnConfiguration.ps1
# Author : Keith Ramsey
# =============================================================================
# Change Log
# -----------------------------------------------------------------------------
# 2026-05-09 Keith Ramsey Phase 2 release polish - DR-202 standard header applied.
# 2026-05-17 Keith Ramsey DR-308: pass-through -PassThru. When set, returns the
# engine's SqlSpn.ExecutionResult to the caller.
# Additive; default behaviour unchanged.
# 2026-05-21 Keith Ramsey DR-309 v1 surface narrowing: -Scenario reduced to
# Standalone,AlwaysOn,FCI (no MSDTC); -Role reduced
# to Engine,Agent (no SSAS,SSRS,Browser). Help text
# updated to match the v1.4.0 surface.
# =============================================================================
function Start-SqlSpnConfiguration {
    <#
    .SYNOPSIS
        Programmatic, parameterized entry point for SPN registration with policy compliance.
    .DESCRIPTION
        The non-interactive counterpart to Start-SqlSpnManager. Composes the full
        registration pipeline from named parameters so it can be called from scripts,
        automation, or scheduled tasks:

            Resolve-SqlPolicyFromContext -> Assert-SqlAccountStandard ->
            Get-SqlSpnInfrastructure -> Get-SqlSpnAccount -> New-SqlSpnPlan ->
            Test-SqlSpnPlan -> Invoke-SqlSpnExecutionEngine

        The policy-compliance step (Assert-SqlAccountStandard) is the differentiator
        from a hand-rolled pipeline: it validates that the account follows the
        configured naming/OU/object-class convention before any SPN work begins.

        Honors ShouldProcess via the underlying engine. Use -WhatIf to preview, or
        -Force (which sets -Confirm:$false on Invoke-SqlSpnExecutionEngine) to skip
        all prompts.
    .PARAMETER SamAccountName
        SAM account name to register the SPN against (e.g., svc_sql_prod or SQLFCI01$).
    .PARAMETER Scenario
        Infrastructure type: Standalone, AlwaysOn, or FCI. (MSDTC deferred per DR-309.)
    .PARAMETER Role
        SQL service role: Engine or Agent. (SSAS, SSRS, Browser deferred per DR-309.)
    .PARAMETER TargetName
        DNS name of the server, virtual computer, or AG listener.
    .PARAMETER ManualPort
        Override automatic port discovery.
    .PARAMETER InstanceName
        SQL instance name. Defaults to MSSQLSERVER (default instance).
    .PARAMETER VirtualComputerAccount
        Optional explicit VCO override for FCI Engine registrations.
    .PARAMETER UseGmsa
        Indicates the account is a group Managed Service Account (selects the gMSA policy variant).
    .PARAMETER SkipCompliance
        Bypass the Assert-SqlAccountStandard policy check. Use when the policy table
        doesn't yet cover the role/scenario combination.
    .PARAMETER Force
        Skip ShouldProcess confirmation prompts during execution.
    .PARAMETER PassThru
        Opt-in. Return the engine's structured SqlSpn.ExecutionResult to the
        caller (see Invoke-SqlSpnExecutionEngine for the shape, locked by
        DR-308). Off by default so existing scripted callers see no behaviour
        change. The console output and audit log are written exactly as before.
    .EXAMPLE
        Start-SqlSpnConfiguration -SamAccountName svc_sql_prod -Scenario Standalone -Role Engine -TargetName SQLSRV01
    .EXAMPLE
        Start-SqlSpnConfiguration -SamAccountName SQLFCI01$ -Scenario FCI -Role Engine -TargetName SQLFCI01 -Force
    .EXAMPLE
        $r = Start-SqlSpnConfiguration -SamAccountName svc_sql_prod -Scenario Standalone -Role Engine -TargetName SQLSRV01 -Force -PassThru
        if ($r.OverallStatus -eq 'PartialFailure') { $r.Spns | Where-Object Action -eq 'Failed' }
    .OUTPUTS
        Without -PassThru: none. Side effects: SPN registrations and audit log
        entries. With -PassThru: one SqlSpn.ExecutionResult object (see
        Invoke-SqlSpnExecutionEngine), in addition to the unchanged side effects.
    .NOTES
        For interactive operator use, see Start-SqlSpnManager.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$true)][string]$SamAccountName,
        [Parameter(Mandatory=$true)][ValidateSet('Standalone','AlwaysOn','FCI')][string]$Scenario,
        [Parameter(Mandatory=$true)][ValidateSet('Engine','Agent')][string]$Role,
        [Parameter(Mandatory=$true)][string]$TargetName,
        [int]$ManualPort,
        [string]$InstanceName = 'MSSQLSERVER',
        [PSCustomObject]$VirtualComputerAccount,
        [switch]$UseGmsa,
        [switch]$SkipCompliance,
        [switch]$SkipPreflight,
        [switch]$Force,
        [switch]$PassThru
    )

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

    if (-not $SkipCompliance) {
        $policyName = Resolve-SqlPolicyFromContext -Scenario $Scenario -Role $Role -UseGmsa:$UseGmsa
        Assert-SqlAccountStandard -SamAccountName $SamAccountName -PolicyName $policyName
    }

    $infraParams = @{ Scenario = $Scenario; TargetName = $TargetName; InstanceName = $InstanceName }
    if ($PSBoundParameters.ContainsKey('ManualPort')) { $infraParams.ManualPort = $ManualPort }
    $infra = Get-SqlSpnInfrastructure @infraParams

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

    $planParams = @{ VerifiedAccount = $account; Infrastructure = $infra; Role = $Role }
    if ($VirtualComputerAccount) { $planParams.VirtualComputerAccount = $VirtualComputerAccount }
    $plan = New-SqlSpnPlan @planParams
    if (-not $plan) { return }

    $validation = $plan | Test-SqlSpnPlan
    if ($validation.Status -ne 'Clear') {
        throw "Plan validation failed: $($validation.Status) - $($validation.Reason)"
    }

    if ($PassThru) {
        # Opt-in programmatic path (DR-308). The engine does its own per-SPN
        # ShouldProcess (so -WhatIf still yields SkippedWhatIf and -Confirm
        # still prompts per SPN); we hand its structured result straight back
        # to the caller. The wrapper's aggregate ShouldProcess line is skipped
        # only here so a machine consumer gets the result object, not a prompt.
        # The default path below is byte-identical to prior behaviour.
        return ($plan | Invoke-SqlSpnExecutionEngine -Confirm:(-not $Force) -SkipPreflight:$SkipPreflight -PassThru)
    }

    if ($PSCmdlet.ShouldProcess("$($plan.ProposedSpns.Count) SPN(s) on $($plan.AccountDn)", 'Register SPNs')) {
        $plan | Invoke-SqlSpnExecutionEngine -Confirm:(-not $Force) -SkipPreflight:$SkipPreflight
    }
}