SpnManager/how-it-works:5
Kerberos broken now? →
how it works

Sense, plan, test, execute, prove.

One lifecycle for every provider. Each stage is a separately callable function registered in providers.json; Invoke-SpnLifecycle is thin glue for a human or CLI caller. Audit-only providers stop at Test and hand off instead of executing.

01sense
02plan
03test
04executeor hand off
05prove
01 / sense

Discover what is actually running.

The provider reads the service the way an administrator would if they had time: SQL Server from Win32_Service and the instance port in the registry; IIS from site bindings and app-pool identities, grouped by account rather than by site; SSRS from rsreportserver.config. No user input for standard configurations.

Sense produces candidates: the service, the host variants a client might ask for, the account that should hold the SPN.

PowerShell
Get-SpnProvider -ProviderId SQL.Engine# ProviderId      SQL.Engine# ServiceClass    MSSQLSvc# PortInSPN       True   (from registry)# HostVariants    FQDN, NetBIOS# Capability      SpnWrite# ProofOracle     SqlAuthScheme
02 / plan

Build the exact strings, and only those.

The plan is the full expected SPN set for the discovered configuration, on the account that should carry it. For SQL Server that is FQDN and NetBIOS, with and without port. For SSAS it never contains a port; the colon suffix is the instance name. For HTTP it never contains 80 or 443.

The plan is data. You can read it, diff it, and reject it before anything touches AD.

plan
# SpnPlan for CORP\svc_sql on SQL01MSSQLSvc/sql01.corp.example.com:1433MSSQLSvc/SQL01:1433MSSQLSvc/sql01.corp.example.comMSSQLSvc/SQL01# Owner            CORP\svc_sql   (from SCM)# Scenario         AlwaysOn — listener FQDN added
03 / test

Check the plan against AD before anyone writes.

Test-SpnPlan runs the preflight: does the caller have write rights to the target account; does any proposed SPN already exist elsewhere in the forest; is the owner the account the service really runs as. A duplicate found here is a finding, not a surprise at 2 a.m.

PowerShell
$plan | Test-SpnPlan  ACL write on CN=svc_sql        ok  duplicate scan (forest)         1 conflict    MSSQLSvc/SQL01:1433  also on  SQL01$  owner matches SCM identity      ok# Plan is not executable until the conflict is resolved.
04 / execute, or hand off

Write to AD only when you asked it to. Otherwise, write the runbook.

Execution is ShouldProcess-gated: -WhatIf shows the change, -Confirm asks. Every action is logged with provenance: module version, commit, run ID, timestamp.

Where the caller has no AD write rights, or the provider is audit-only, the same plan becomes a hand-off: a setspn bundle for the AD team, or an operator runbook for the far-side host. Keytab services always take the runbook path. The safety model →

PowerShell
Invoke-SpnLifecycle -ProviderId SQL.Engine -WhatIfWhat if: Performing the operation "Add SPN" on target "CN=svc_sql,...".Invoke-SpnLifecycle -ProviderId AD.RDP -TargetComputer srv01 -PassThru# audit-only provider → SpnAuditResult + hand-off bundle$plan | Export-SpnRegistrationScript -OutFile .\spn-srv01.ps1
05 / prove

Ask the service what mechanism it saw.

The proof harness records the negotiated mechanism from an independent client before the change and after it, and requires a second oracle to agree. A run passes only if the baseline was NTLM and the result is Kerberos. A baseline that was already Kerberos yields Inconclusive.

This stage is how the product knows it worked, rather than assuming that a successful LDAP write means Kerberos is fixed.

beforeafteroracle 2verdict
sql01 / SqlAuthSchemeNTLMKERBEROSagreesPASS
web01 / HttpSpnegoKERBEROSKERBEROSagreesINCONCLUSIVE

next
Proof and evidence →Which providers execute and which hand off →Quickstart →