Deploying certificates to Failover cluster

Deploying external certificates to multple clusters with many roles can be a pain, especially when the Windows Domain won't listen. With this script you can deploy them to any Windows Server host (2016+ && WinRM enabled on a secure management subnet). With per use prompt
<#
DeployWildcard.ps1
-------------------
Bulk‑deploy a renewed wildcard certificate (PFX) to cluster
node and/or VM, with per‑server approval and IIS rebinding.
- Requires RSAT‑Failover‑Clustering on the admin workstation.
- PFX travels over WinRM.
- Works on Windows Server 2016+ hosts and guests.
- IIS Bind is commented out and not tested
#>
# ----- IMPORT LOCAL MODULES -----------------------------------------------------
Import-Module FailoverClusters
# ----- VARIABLES ----------------------------------------------------------------
$pfxPath = 'C:\temp\wildcard.2024.pfx'
$clusters = @('PROD-CL01','SQLPROD-CL01')
$includeNodes = $false # push to hardware nodes
$includeVMs = $true # push to VMs
# --------------------------------------------------------------------------------
# 1. Ask once for the PFX password
$pfxPass = Read-Host 'Password for the PFX' -AsSecureString
# 2. Read the PFX into memory once (byte array)
$pfxBytes = Get-Content $pfxPath -Encoding Byte
# 3. Build target list
$targets = @()
if ($includeNodes) {
foreach ($c in $clusters) {
$targets += Get-ClusterNode -Cluster $c |
Where-Object {$_.State -eq 'Up'} |
Select-Object -ExpandProperty Name
}
}
if ($includeVMs) {
foreach ($c in $clusters) {
$targets += Get-ClusterGroup -Cluster $c |
Where-Object {$_.GroupType -eq 'VirtualMachine'} |
Select-Object -ExpandProperty Name
}
}
$servers = $targets | Sort-Object -Unique
if (-not $servers) {
Write-Warning 'No servers discovered'
exit
}
# 4. Loop with Y/N/Q prompt
foreach ($srv in $servers) {
$resp = Read-Host "Deploy certificate to [$srv]? (Y=yes, N=skip, Q=quit)"
switch ($resp.ToUpper()) {
#---------------------------------------------------------------- deploy
{ $_ -eq '' -or $_ -eq 'Y' } {
Write-Host " -> deploying to $srv …"
try {
Invoke-Command -ComputerName $srv -ErrorAction Stop -ScriptBlock {
param([byte[]]$bytes,[SecureString]$pwd)
# ‑‑ save PFX to temp & import
$tmp = "$env:TEMP\renewed.pfx"
[IO.File]::WriteAllBytes($tmp,$bytes)
$cert = Import-PfxCertificate -FilePath $tmp `
-CertStoreLocation 'Cert:\LocalMachine\My' `
-Password $pwd
Remove-Item $tmp -Force
# ‑‑ Unsafe & incorrect, check for IIS en rebind. Rather not override, we have a couple of months to set manually which is safer
#if ((Get-WindowsFeature -Name Web-Server).InstallState -eq 'Installed') {
# Import-Module IISAdministration
# Get-IISSite | ForEach-Object {
# foreach ($b in $_.Bindings |
# Where-Object {$_.protocol -eq 'https' -and
# $_.CertificateThumbprint -ne $cert.Thumbprint}) {
# Set-IISSiteBinding -Name $_.Name `
# -BindingInformation $b.BindingInformation `
# -CertificateStoreName 'MY' `
# -CertificateThumbprint $cert.Thumbprint
# }
# }
#}
'SUCCESS'
} -ArgumentList $pfxBytes,$pfxPass | Out-Null
Write-Host " v $srv updated" -ForegroundColor Green
}
catch {
Write-Warning " x $srv failed — $($_.Exception.Message)"
}
}
#---------------------------------------------------------------- skip
'N' {
Write-Host " - skipped $srv"
}
#---------------------------------------------------------------- quit
'Q' {
Write-Host 'Quitting at user request.' -ForegroundColor Yellow
exit
}
#---------------------------------------------------------------- default
default {
Write-Host ' (unrecognised response, skipping)'
}
}
}
Write-Host 'End of list reached, done.'
Also works for Azure stack HCI failover cluster