Public/Remove-SqlSpn.ps1
|
# ============================================================================= # Script : Remove-SqlSpn.ps1 # Author : Keith Ramsey # ============================================================================= # Change Log # ----------------------------------------------------------------------------- # 2026-05-09 Keith Ramsey Phase 2 release polish - DR-202 standard header applied. # 2026-05-14 Keith Ramsey Array args; target AccountName (setspn cannot # resolve a DN); log SUCCESS only when setspn -D # actually succeeded. # 2026-05-15 Keith Ramsey Success verdict delegated to Private\ # Invoke-SqlSpnSetspnAction with -IgnoreDuplicate # (a duplicate is not a removal failure). # ============================================================================= function Remove-SqlSpn { <# .SYNOPSIS Deregisters each SPN in a plan from the plan's AccountDn (primitive). .DESCRIPTION Iterates the plan's ProposedSpns and calls setspn -D for each one, then writes a SUCCESS entry to the audit log. This is the deregistration counterpart to Add-SqlSpn. Used to clean up stale SPNs after a service identity change or a server decommission. Honors ShouldProcess, so -WhatIf and -Confirm work. Use -WhatIf first when decommissioning to confirm the SPN list before pulling. .PARAMETER SpnPlan Plan object describing which SPNs to remove and from which account. Required fields: AccountName, ProposedSpns. The same New-SqlSpnPlan output used to register can be piped here to unregister. .EXAMPLE $plan | Remove-SqlSpn -WhatIf .EXAMPLE $plan | Remove-SqlSpn -Confirm:$false .OUTPUTS None. Side effects: setspn -D calls and audit log entries. .NOTES For compatibility with Add-SqlSpn, this command does not run a forest-wide existence check before attempting removal. setspn -D is a no-op (with a warning) if the SPN doesn't exist. #> [CmdletBinding(SupportsShouldProcess)] param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][PSCustomObject]$SpnPlan) process { foreach ($Spn in $SpnPlan.ProposedSpns) { if ($PSCmdlet.ShouldProcess("REMOVING $Spn from $($SpnPlan.AccountName)")) { # -IgnoreDuplicate: removing one of a duplicate pair is success, # not failure (unlike registration). This asymmetry is the bug # the shared helper makes explicit instead of a silent regex diff. $r = Invoke-SqlSpnSetspnAction -ArgumentList @('-D', $Spn, $SpnPlan.AccountName) -IgnoreDuplicate if ($r.Failed) { Write-Error "FAILED to remove $Spn from $($SpnPlan.AccountName). setspn: $($r.Output)" Write-SqlSpnLog -Message "FAILED to remove $Spn from $($SpnPlan.AccountName). setspn: $($r.Output)" -Level ERROR } else { Write-SqlSpnLog -Message "DECOMMISSIONED: $Spn from $($SpnPlan.AccountName)" -Level SUCCESS } } } } } |