SqlSpnManager

How SqlSpnManager works

Six cards. Click through them to see what each part of SqlSpnManager does — in plain English, with a small picture for each idea.

Card 1 of 6

Find out what's actually here

Discovery first. SPN work without knowing the identities is guessing.

Before SqlSpnManager registers anything, it asks the box what SQL services it's running and what identities they use. One command, one table: Get-SqlSpnDiscoveryEngine.

If the table doesn't match what you expected, stop and find out why before you register a single SPN. Wrong identity = wrong SPN = the same Kerberos error in a different costume.

SQL HOST MSSQLSERVER svc_sql_prod MSSQL$ARCH svc_sql_arch MSSQL$REPORTS svc_sql_rep discover DISCOVERY RESULT Instance Account Port MSSQLSERVER svc_sql_prod 1433 ARCH svc_sql_arch 1450 REPORTS svc_sql_rep 1500 Names, accounts, ports — from the box itself.
What the call actually returns
PS> Get-SqlSpnDiscoveryEngine

ServiceName       AccountName      Instance      Port  Status
-----------       -----------      --------      ----  ------
MSSQLSERVER       svc_sql_prod     MSSQLSERVER   1433  Running
MSSQL$ARCH        svc_sql_arch     ARCH          1450  Running
MSSQL$REPORTS     svc_sql_rep      REPORTS       1500  Stopped

One row per local SQL service. Source: Win32_Service + the registry. No AD calls in this step.

Card 2 of 6

The right SPNs, deterministically

No more "which form do I need?" — the planner emits all four.

The trap most DBAs hit at least once: registering only the FQDN form (or only the NetBIOS form, or only the port-bearing one). Kerberos needs all four for full client-coverage.

New-SqlSpnPlan takes the verified account, the resolved infrastructure, and the role; out comes a plan object with all four canonical SPN strings. No guessing, no manual enumeration.

Account Infrastructure Role: Engine New-SqlSpn Plan MSSQLSvc/SRV:1433 MSSQLSvc/SRV.fqdn:1433 MSSQLSvc/SRV MSSQLSvc/SRV.fqdn All four forms, every time. No "which one again?"
The plan object
$plan = New-SqlSpnPlan -VerifiedAccount $acct `
                       -Infrastructure $infra -Role Engine
$plan.ProposedSpns
# MSSQLSvc/SQLSRV01:1433
# MSSQLSvc/SQLSRV01.example.local:1433
# MSSQLSvc/SQLSRV01
# MSSQLSvc/SQLSRV01.example.local

Plus a PlanGuid, AccountDn, AccountName (sAMAccountName for setspn), Scenario, TargetDomain, and CrossForest fields. The engine reads from this object.

Card 3 of 6

Check the forest before you write

No accidental duplicates. Locale-independent.

Before any setspn -S, the engine runs setspn -Q forest-wide on the SPN it's about to register. If anyone in the forest already holds it, the engine logs a WARN, skips that one, and moves on.

Detection is locale-independent (DR-304): matches on the echoed SPN that setspn -Q prints under the holding account, not the English-only "Existing SPN found!" banner.

CHECKING MSSQLSvc/SRV:1433 AD FOREST setspn -Q (forest-wide) CLEAR Proceed to setspn -S CONFLICT WARN, skip, log Forest check first — always.
Why locale-independent matters
# Older / English-only logic (Bug F in the 2026-05-14 lab):
#   if ($output -match 'Existing SPN found!') { conflict }
# That breaks on French / German / Japanese Windows.
#
# DR-304 fix:
#   if ($output -join "`n" -match [regex]::Escape($spn)) { conflict }
# Echoed SPN strings aren't localized; the banner is.

The matching now works on the SPN itself, not on the surrounding human-language text. Tests/Unit/SpnLogic.Tests.ps1 has a French-locale regression test that pins this.

Card 4 of 6

Honest about your AD rights

Predict before you attempt. Then warn or proceed.

Before any registration, Test-SqlAdPermission reads the target object's nTSecurityDescriptor and walks the DACL for an Allow ACE granting the SPN-write extended right, GenericAll, or WriteProperty to your identity (or one of your groups).

If you have the rights, registration proceeds. If you don't, the engine emits a structured WARN saying so — and continues anyway, because the static check can miss delegation patterns it doesn't understand. setspn will surface the actual error.

YOU DOM\keith may I? TARGET ACCOUNT svc_sql_prod DACL: Allow DOM\keith WriteProperty (servicePrincipalName) ALLOWED Method: write-property Predicted. Then attempted. Then verified.
Skip the preflight when you know better
# When you know your environment uses a delegation pattern
# Test-SqlAdPermission can't model, skip it:
$plan | Invoke-SqlSpnExecutionEngine -SkipPreflight -Confirm:$false

setspn still runs. If it fails, the engine catches the failure and reports it in the structured result. DR-108: warn-and-continue, never hard-block on a static-check guess.

Card 5 of 6

FCI does the right thing

SPNs land on the Virtual Computer Object, not the service account.

Common failure mode: register SPNs against the SQL service account, then watch them evaporate after the next FCI failover. The cluster owns the virtual name; SPNs against the service account move with the account, not the virtual.

SqlSpnManager detects -Scenario FCI, looks up the cluster Virtual Computer Object from the virtual name (Resolve-SqlSpnFciCno), warns you about the redirect, and registers against the VCO. SPNs now survive failover.

SERVICE ACCOUNT svc_sql_fci would evaporate at failover redirect CLUSTER VCO SQLFCI01$ SPNs survive failover DR-006: auto-resolved from the virtual name.
When auto-resolve isn't enough
# If multiple VCOs match (rare; usually old cluster objects):
$vco = Get-SqlSpnAccount -SamAccountName 'SQLFCI01$'
New-SqlSpnPlan -VerifiedAccount $svc -Infrastructure $infra `
               -Role Engine -VirtualComputerAccount $vco

The explicit override must be a computer object (ObjectClass = 'computer'); the planner rejects a user-object override with a structured error.

Card 6 of 6

When you don't own AD

Render a clean script. Hand it to your AD admin. Audit included.

In a regulated environment, or anywhere DBA and AD are separate teams: you don't have rights to write SPNs yourself. The historical workaround is to hand-craft setspn commands in a chat message and hope they get run correctly.

Export-SqlSpnRegistrationScript renders the plan into a clean .cmd (or .ps1) bundle. The bundle's header carries provenance — module version, plan GUID, UTC stamp, target sAMAccountName, target DN — so your AD admin can audit exactly what they're about to run.

DBA register.cmd REM provenance: REM v1.4.0 REM plan-guid REM utc-stamp REM svc_sql_prod setspn -S ... setspn -S ... AD ADMIN EXECUTES FROM ELEVATED SESSION setspn -S MSSQLSvc/... svc_sql_prod DR-311: closes the loop for AD-segregated shops.
The end-of-walkthrough recap

Six commands you'll actually use (plus 7 supporting). Three lab-proven scenarios (Standalone, AlwaysOn, FCI). Forest-wide duplicate detection. ACL preflight. FCI VCO redirect. AD-admin handoff bundle with provenance. Per-invocation audit log + Windows Event Log. 213/213 Pester tests. That's SqlSpnManager.

Read the full how-to →

1 of 6