Public/Get-SqlSpnInfrastructure.ps1

# =============================================================================
# Script : Get-SqlSpnInfrastructure.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: -Scenario reduced to
# Standalone,AlwaysOn,FCI (no MSDTC).
# =============================================================================
function Get-SqlSpnInfrastructure {
    <#
    .SYNOPSIS
        Resolves SPN-relevant infrastructure facts for a target SQL host or virtual name.
    .DESCRIPTION
        Builds the Infrastructure object that New-SqlSpnPlan consumes. Resolves
        FQDN from shortname using the local domain, sniffs the actual TCP port via
        Get-SqlActualPort (registry), detects cross-forest registration by comparing
        the target's DNS suffix to the local USERDNSDOMAIN, and propagates the
        scenario tag (Standalone / AlwaysOn / FCI) through to the plan builder.
    .PARAMETER Scenario
        The infrastructure type: Standalone, AlwaysOn, or FCI. (MSDTC deferred per DR-309.)
    .PARAMETER TargetName
        The DNS name (or shortname) of the server, virtual computer, or listener.
    .PARAMETER ManualPort
        Override the automatic port discovery.
    .PARAMETER InstanceName
        SQL instance name. Defaults to MSSQLSERVER (default instance).
    .EXAMPLE
        Get-SqlSpnInfrastructure -Scenario Standalone -TargetName SQLSRV01
    .EXAMPLE
        Get-SqlSpnInfrastructure -Scenario FCI -TargetName SQLFCI01.contoso.com -ManualPort 55001
    .OUTPUTS
        PSCustomObject with NetBIOS, FQDN, Port, Scenario, InstanceName,
        TargetDomain, CrossForest.
    .NOTES
        Cross-forest detection compares the target's DNS suffix to $env:USERDNSDOMAIN.
        On a workgroup or non-domain-joined host the cross-forest flag is conservative
        (false) and downstream callers may need to manually set the -T flag via
        Invoke-SqlSpnNativeCall.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][ValidateSet('Standalone', 'AlwaysOn', 'FCI')][string]$Scenario,
        [Parameter(Mandatory=$true)][string]$TargetName,
        [int]$ManualPort,
        [string]$InstanceName = 'MSSQLSERVER'
    )

    $LocalDomain = $env:USERDNSDOMAIN
    $TargetParts = $TargetName -split '\.', 2
    $NetBIOS     = $TargetParts[0]
    $TargetDomain = if ($TargetParts.Count -gt 1) { $TargetParts[1] } else { $LocalDomain }
    $FQDN        = if ($TargetParts.Count -gt 1) { $TargetName } elseif ($LocalDomain) { "$NetBIOS.$LocalDomain" } else { $NetBIOS }

    $FinalPort = if ($PSBoundParameters.ContainsKey('ManualPort')) {
        $ManualPort
    }
    else {
        Get-SqlActualPort -ComputerName $TargetName -InstanceName $InstanceName
    }

    $CrossForest = if ($LocalDomain -and $TargetDomain -and ($LocalDomain -ne $TargetDomain)) { $true } else { $false }

    return [PSCustomObject]@{
        NetBIOS      = $NetBIOS
        FQDN         = $FQDN
        Port         = $FinalPort
        Scenario     = $Scenario
        InstanceName = $InstanceName
        TargetDomain = $TargetDomain
        CrossForest  = $CrossForest
    }
}