Deploying certificates to Failover cluster

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