param( [Parameter(Mandatory = $true)] [string]$MachineName, [Parameter(Mandatory = $true)] [string]$ServiceAccount, [int]$SqlPort = 1433, [string[]]$HadrListeners ) Import-Module ActiveDirectory Write-Host "=== MSSQLSvc + HADR SPN Validation Tool ===" -ForegroundColor Cyan Write-Host "Machine: $MachineName" Write-Host "Service Account: $ServiceAccount" Write-Host "SQL Port: $SqlPort" if ($HadrListeners) { Write-Host "HADR Listener(s): $($HadrListeners -join ', ')" } Write-Host "-------------------------------------------" # Get domain FQDN $domainFQDN = (Get-ADDomain).DNSRoot # Lookup service account (user or computer) try { $svcObj = Get-ADUser -Identity $ServiceAccount -Properties ServicePrincipalName -ErrorAction Stop } catch { try { $svcObj = Get-ADComputer -Identity $ServiceAccount -Properties ServicePrincipalName -ErrorAction Stop } catch { Write-Host "ERROR: Service account '$ServiceAccount' not found in AD." -ForegroundColor Red exit } } # Build expected MSSQLSvc SPNs for machine and HADR listeners $namesToCheck = @($MachineName) if ($HadrListeners) { $namesToCheck += $HadrListeners } $expectedSPNs = foreach ($name in $namesToCheck) { "MSSQLSvc/${name}.${domainFQDN}:$SqlPort" "MSSQLSvc/${name}:$SqlPort" } Write-Host "`nExpected MSSQLSvc SPNs:" -ForegroundColor Yellow $expectedSPNs | ForEach-Object { Write-Host " $_" } # Case-insensitive missing SPN check (fixed grouping) $missingSPNs = $expectedSPNs | Where-Object { $expectedLower = $_.ToLower() -not (( $svcObj.ServicePrincipalName | ForEach-Object { $_.ToLower() } ) -contains $expectedLower) } if ($missingSPNs) { Write-Host "`n[!] Missing MSSQLSvc SPNs on service account:" -ForegroundColor Red $missingSPNs | ForEach-Object { Write-Host " $_" -ForegroundColor Red } $addPrompt = Read-Host "`nDo you want to add the missing SPNs now? (Y/N)" if ($addPrompt -match '^[Yy]$') { foreach ($spn in $missingSPNs) { Write-Host "Adding SPN: $spn" -ForegroundColor Green # Use -S to avoid duplicates, requires running as an account with permission setspn -S $spn $ServiceAccount } Write-Host "`n[OK] Missing SPNs have been added." -ForegroundColor Green } else { Write-Host "Skipping SPN creation." -ForegroundColor Yellow } } else { Write-Host "`n[OK] All expected MSSQLSvc SPNs are present." -ForegroundColor Green } # Pull all SPNs in the domain $allSPNs = Get-ADObject -Filter {ServicePrincipalName -like "*"} -Properties ServicePrincipalName | ForEach-Object { foreach ($spn in $_.ServicePrincipalName) { [PSCustomObject]@{ SPN = $spn ADObject = $_.SamAccountName DN = $_.DistinguishedName } } } # Check for duplicates of MSSQLSvc SPNs Write-Host "`nChecking for duplicate MSSQLSvc SPNs in the domain..." -ForegroundColor Yellow foreach ($spn in $svcObj.ServicePrincipalName | Where-Object { $_ -like "MSSQLSvc/*" }) { $owners = $allSPNs | Where-Object { $_.SPN -eq $spn } if ($owners.Count -gt 1) { Write-Host "[DUPLICATE] SPN '$spn' is owned by multiple objects:" -ForegroundColor Red $owners | ForEach-Object { Write-Host " -> $($_.ADObject) ($($_.DN))" } } } # Check for MSSQLSvc SPNs on service account that aren't expected $expectedLowerList = $expectedSPNs | ForEach-Object { $_.ToLower() } $wrongSPNs = $svcObj.ServicePrincipalName | Where-Object { $_ -like "MSSQLSvc/*" -and ($_.ToLower() -notin $expectedLowerList) } if ($wrongSPNs) { Write-Host "`n[!] Warning: Service account has MSSQLSvc SPNs not in expected list:" -ForegroundColor Red $wrongSPNs | ForEach-Object { Write-Host " $_" } } else { Write-Host "`n[OK] No unexpected MSSQLSvc SPNs found." -ForegroundColor Green } Write-Host "`n=== MSSQLSvc + HADR SPN Check Complete ==="