Private/Invoke-SqlSpnSetspnAction.ps1

# =============================================================================
# Script : Invoke-SqlSpnSetspnAction.ps1
# Author : Keith Ramsey
# =============================================================================
# Change Log
# -----------------------------------------------------------------------------
# 2026-05-15 Keith Ramsey New: single home for the setspn write + truthful
# success verdict that was triplicated across
# Invoke-SqlSpnExecutionEngine, Add-SqlSpn, and
# Remove-SqlSpn (one copy had already diverged). One
# place for the verdict means a future DR-307
# resolution changes exactly one function.
# =============================================================================
function Invoke-SqlSpnSetspnAction {
    <#
    .SYNOPSIS
        Runs one setspn write (-S / -D) and returns a truthful success verdict.
    .DESCRIPTION
        Wraps Invoke-SqlSpnNativeCall and decides success from the native exit
        code OR a set of failure signatures in the output. setspn.exe is an
        unreliable exit-code citizen across Windows builds (DR-303), so the
        signature check is a deliberate second signal, biased toward declaring
        failure when uncertain � a false success is the worst outcome for an
        SPN tool.

        Never throws: a missing setspn.exe (command-not-found) is returned as a
        structured failure, not an unhandled terminating error.

        NOTE (DR-307, OPEN): the failure signatures are English and setspn's
        exit code is not fully reliable, so on a non-English Windows a real
        failure could still be misread. The locale-independent fix (post-write
        AD verification) is a pending decision; when it lands it changes only
        this function.
    .PARAMETER ArgumentList
        setspn argument tokens, one element per argv token (e.g.
        @('-S','MSSQLSvc/host:1433','SAM$') or @('-T','dom','-D',$spn,$sam)).
    .PARAMETER IgnoreDuplicate
        Treat a 'Duplicate SPN' message as NOT a failure. Used by Remove-SqlSpn:
        removing one of a duplicate pair is success, not failure. Add-SqlSpn and
        the engine omit this so a duplicate-on-register is a real failure.
    .OUTPUTS
        PSCustomObject { Failed = [bool]; Output = [string] }
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string[]]$ArgumentList,
        [switch]$IgnoreDuplicate
    )

    $signatures = @(
        'Unable to locate account'
        'FindDomainForAccount'
        '0x[0-9A-Fa-f]{8}'
        '\bfailed\b'
    )
    if (-not $IgnoreDuplicate) { $signatures += 'Duplicate SPN' }
    $pattern = $signatures -join '|'

    $global:LASTEXITCODE = 0
    try {
        $out = & Invoke-SqlSpnNativeCall $ArgumentList 2>&1
    }
    catch {
        return [PSCustomObject]@{ Failed = $true; Output = "setspn could not be invoked: $($_.Exception.Message)" }
    }
    $text = ($out | Out-String).Trim()

    $failed = ($LASTEXITCODE -ne 0) -or ($text -match $pattern)
    return [PSCustomObject]@{ Failed = $failed; Output = $text }
}