The following PowerShell snippet can be used to quickly install an SSL (or TLS) certificate in Windows Server. It assumes you have a PFX file and its password. The default location is Cert:\LocalMachine\My, to use for IIS websites.
The following PowerShell snippet can be used to quickly install an SSL (or TLS) certificate in Windows Server. It assumes you have a PFX file and its password. The default location is Cert:\LocalMachine\My, to use for IIS websites.
In this script you have to adjust a few bits for every certificate you install, so use it as a base or starting point for your own scripting. After importing the certificate, it also changes the .FriendlyName visible in MMC and IIS Manager, doing so you always know exactly which certificate you need to select. Neat, right? >:-)
#
# Install SSL certificate in Windows with PowerShell
#
$certificatename = "CHANGEME.pfx"
$mypwd = Get-Credential -UserName "${certificatename} SSL cert" -Message 'Enter password below'
$params = @{
FilePath = \\TSCLIENT\C\Users\Jan\SSL\${certificatename}
CertStoreLocation = 'Cert:\LocalMachine\My'
Password = $mypwd.Password
}
$cert = Import-PfxCertificate @params
[string]$thumbprint = $cert.Thumbprint
(Get-ChildItem -Path Cert:\LocalMachine\My\${thumbprint}).FriendlyName = "${certificatename} 2024"
A couple of things happen here:
- First, I define a variable for my certificate name,
- And secondly I use
Get-Credentialto store the certificate password into a variable. This way, the SSL / TLS certificate password is not stored in your PowerShell history file. - Thirdly, we define the certificate parameters. And as you can see by the FilePath value, I use a remote desktop
\\TSCLIENTshare to point to my local computer. Doing so allows me to have certificates stored at one location on my computer (because you know, clients always sent them by email), and use the RDP share to transfer the certificate to the server. At last we define the Certificate Store location (as displayed:Cert:\LocalMachine\My) and the password. - Last, but not least: all this is fed to
Import-PfxCertificate, which in its turn saves the object into$certand this allows us to change its.FriendlyNameproperty.
You can find more information about Import-PfxCertificate on Microsoft Learn and the code shown above is also available as a Gist: https://gist.github.com/Digiover/a2db4125efb069d62a8fedcd23212103.
Psst, did you know you can use certutil -v -dump certificate_name.pfx to look up and verify an SSL certificate’s Common Name (Subject) and Subject Alternative Name (SAN)? Or in PowerShell, you can use:
Get-PfxCertificate certificate_name.pfx | Select-Object Subject,DnsNameList
Bind SSL certificate to website in IIS using netsh
Once your new certificate is installed, you bind the SSL certificate to the website requiring it. In my workflow I compare the thumbprint of current installed certificate with the output I get from netsh.exe http show sslcert. If it matches, I have a certificate that needs updating. This means you must have an administration of thumbprints. Adjust the code to your environment.
The first step is to retrieve a list of all installed certificates in your certificate store with some properties we find interesting. For this step use the following code:
$TxtBindings = (& C:\Windows\System32\netsh.exe http show sslcert) | select-object -skip 3 | out-string
$nl = [System.Environment]::NewLine
$Txtbindings = $TxtBindings -split "$nl$nl"
$BindingsList = foreach ($Binding in $Txtbindings) {
if ($Binding -ne "") {
$Binding = $Binding -replace " ", "" -split ": "
[pscustomobject]@{
IPPort = ($Binding[1] -split "`n")[0]
CertificateHash = ($Binding[2] -split "`n" -replace '[^a-zA-Z0-9]', '')[0]
AppID = ($Binding[3] -split "`n")[0]
CertStore = ($Binding[4] -split "`n")[0]
}
}
}
This code comes partly from Kelvin Tegelaar’s blogpost Monitoring with PowerShell: Monitor SSL certificates.
Once you have $BindingsList, parse it line by line and compare the .CertificateHash property with your currently installed certificate thumbprint (hash, $TlsCertOldThumbprint in the code below). A match means the certificate needs updating. First remove the old certificate HTTP binding and secondly, add a new HTTP binding with the new certificate. All using netsh http.
$TlsCertOldThumbprint = ""
foreach($line in $BindingsList) {
if($line.CertificateHash -eq "$TlsCertOldThumbprint") {
$HostnamePort = $(${line}.IPPort).Trim()
&C:\Windows\System32\netsh.exe http delete sslcert hostnameport=$HostnamePort
if($LASTEXITCODE -ne 0) {
Write-Host "[!!] Error removing certificate"
} else {
# any valid GUID will do, generate one with:
# [guid]::NewGuid().ToString("B")
$appid = '{6ccd10b0-adff-4c3a-83cd-e4e5fb78ba2a}'
&C:\Windows\System32\netsh.exe http add sslcert hostnameport=$HostnamePort certhash=$TlsCertNewThumbprint appid=$appid certstorename=My
if($LASTEXITCODE -ne 0) {
Write-Host "[!!] Error adding certificate"
} else {
Write-Host "New TLS certificate successfully installed on $HostnamePort"
}
}
}
}
Certificate installation script updated by Claude
Claude created the following function to use as a replacement for the above code pieces.
<#
.SYNOPSIS
Replaces HTTP.SYS SSL certificate bindings by thumbprint.
.DESCRIPTION
Finds all HTTP.SYS SSL bindings whose certificate hash matches OldThumbprint
and replaces them with the certificate identified by NewThumbprint, preserving
the original application ID on each binding.
Handles both IP:port and Hostname:port binding types.
Requires administrator privileges.
.PARAMETER OldThumbprint
Thumbprint (certificate hash) of the certificate to replace.
.PARAMETER NewThumbprint
Thumbprint of the replacement certificate; must already be installed
in the certificate store specified by CertStoreName.
.PARAMETER CertStoreName
Certificate store to bind from. Defaults to 'My' (Personal store).
.PARAMETER Port
Optionally limit the operation to specific port numbers (e.g. 443, 30443).
Omit to process all ports.
.EXAMPLE
.\List-HttpSysBindingsHavingSSL.ps1 -OldThumbprint 'ABC123...' -NewThumbprint 'DEF456...'
.EXAMPLE
.\List-HttpSysBindingsHavingSSL.ps1 -OldThumbprint 'ABC123...' -NewThumbprint 'DEF456...' -Port 443, 30443 -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string] $OldThumbprint,
[Parameter(Mandatory)]
[string] $NewThumbprint,
[string] $CertStoreName = 'My',
[string[]] $Port
)
function Get-HttpSysSslBindings {
<#
.SYNOPSIS
Lists HTTP.SYS SSL certificate bindings.
.DESCRIPTION
Parses the output of 'netsh http show sslcert' and returns objects with
the binding address, binding type, certificate hash and application ID
for each SSL binding. Handles both IP:port and Hostname:port binding types.
.PARAMETER Port
One or more port numbers to filter on (e.g. 443, 30443).
Omit to return all SSL bindings regardless of port.
.EXAMPLE
Get-HttpSysSslBindings
.EXAMPLE
Get-HttpSysSslBindings -Port 443, 30443
.OUTPUTS
[pscustomobject] with properties: Binding, BindingType, CertificateHash, ApplicationId
#>
[CmdletBinding()]
[OutputType([pscustomobject])]
param(
[string[]] $Port
)
# Validate and convert Port before anything else; gives a clear message for
# non-numeric input such as '3*443' instead of a raw type-conversion exception
$portNumbers = foreach ($p in $Port) {
[int] $n = 0
if (-not [int]::TryParse($p, [ref]$n) -or $n -lt 1 -or $n -gt 65535) {
Write-Error -Message "Invalid -Port value '$p'. Specify a whole number between 1 and 65535." `
-Category InvalidArgument -TargetObject $p -ErrorAction Stop
}
$n
}
$Port = $portNumbers
# netsh http show sslcert requires administrator privileges
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Error -Message 'Get-HttpSysSslBindings requires administrator privileges.' `
-Category PermissionDenied -ErrorAction Stop
}
$netsh = 'C:\Windows\System32\netsh.exe'
if (-not (Test-Path -LiteralPath $netsh -PathType Leaf)) {
Write-Error -Message "netsh.exe not found at '$netsh'." `
-Category ObjectNotFound -ErrorAction Stop
}
$nl = [System.Environment]::NewLine
$raw = (& $netsh http show sslcert) | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "netsh http show sslcert failed with exit code $LASTEXITCODE. Output: $($raw.Trim())" `
-Category InvalidOperation -ErrorAction Stop
}
# Split into per-binding blocks on blank lines; discard header and empty blocks
$blocks = $raw -split "$nl$nl" | Where-Object { $_ -match '(?:IP|Hostname):port' }
foreach ($block in $blocks) {
# Named capture on type (IP/Hostname) avoids positional fragility
if ($block -notmatch '(?<type>IP|Hostname):port\s+:\s+(?<binding>\S+)') { continue }
$binding = $Matches['binding']
$bindingType = $Matches['type']
# Optional port filter — supports non-standard ports such as :30443
if ($Port) {
$bindingPort = [int]($binding -split ':')[-1]
if ($bindingPort -notin $Port) { continue }
}
[pscustomobject]@{
Binding = $binding
BindingType = $bindingType # 'IP' or 'Hostname'; IP bindings are skipped by Update-HttpSysSslCertificate
CertificateHash = if ($block -match 'Certificate Hash\s+:\s+(?<h>[0-9a-fA-F]+)') { $Matches['h'] } else { $null }
ApplicationId = if ($block -match 'Application ID\s+:\s+(?<id>\S+)') { $Matches['id'] } else { $null }
}
}
}
function Update-HttpSysSslCertificate {
<#
.SYNOPSIS
Replaces HTTP.SYS SSL certificate bindings matching a given certificate hash.
.DESCRIPTION
Calls Get-HttpSysSslBindings to find bindings whose CertificateHash matches
OldCertificateHash, then issues netsh delete + add to replace each one with
the certificate identified by NewCertificateHash. The original ApplicationId
is preserved on each binding. Supports -WhatIf and -Confirm.
.PARAMETER OldCertificateHash
Certificate hash (thumbprint) to replace.
.PARAMETER NewCertificateHash
Certificate hash of the replacement certificate.
.PARAMETER CertStoreName
Certificate store to bind from. Defaults to 'My'.
.PARAMETER Port
Optionally limit the operation to specific port numbers.
.EXAMPLE
Update-HttpSysSslCertificate -OldCertificateHash 'ABC...' -NewCertificateHash 'DEF...'
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string] $OldCertificateHash,
[Parameter(Mandatory)]
[string] $NewCertificateHash,
[string] $CertStoreName = 'My',
[string[]] $Port
)
$netsh = 'C:\Windows\System32\netsh.exe'
$bindings = Get-HttpSysSslBindings -Port $Port
$matched = 0
foreach ($b in $bindings) {
if ($b.CertificateHash -ne $OldCertificateHash) { continue }
$matched++
if ($b.BindingType -eq 'IP') { continue } # ipport= bindings are intentionally skipped
$endpoint = $b.Binding
$bindingParam = "hostnameport=$endpoint"
if (-not $PSCmdlet.ShouldProcess($endpoint, 'Replace SSL certificate')) { continue }
& $netsh http delete sslcert $bindingParam
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "Failed to delete SSL certificate binding on '$endpoint'." `
-Category InvalidOperation
continue
}
# Preserve the original application ID rather than using a hardcoded GUID
& $netsh http add sslcert $bindingParam certhash=$NewCertificateHash appid=$($b.ApplicationId) certstorename=$CertStoreName
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "Failed to add new SSL certificate binding on '$endpoint'." `
-Category InvalidOperation
} else {
Write-Host "SSL certificate successfully replaced on $endpoint."
}
}
if ($matched -eq 0) {
Write-Warning "No SSL bindings found with certificate hash '$OldCertificateHash'."
}
}
# --- Entry point ---
Update-HttpSysSslCertificate `
-OldCertificateHash $OldThumbprint `
-NewCertificateHash $NewThumbprint `
-CertStoreName $CertStoreName `
-Port $Port
This is lot simpler, only Hostname:port bindings are processed.
Summary
- Use the provided PowerShell snippet to install an SSL or TLS certificate on Windows Server using a PFX file and password.
- Adjust variables for each certificate, including defining the FriendlyName for easy identification in MMC and IIS Manager.
- Utilize Get-Credential to securely manage the certificate password and employ a remote desktop share for certificate transfer.
- After installing, bind the SSL certificate to the required website using netsh, comparing thumbprints to ensure accuracy.
- Refer to Microsoft’s documentation for Import-PfxCertificate and find related resources for additional tasks.

Did you like this post?
Please take a second to support Sysadmins of the North and donate! ❤️
Your generosity helps pay for the ongoing costs associated with running this website like coffee, hosting services, library mirrors, domain renewals, time for article research, and coffee, just to name a few. ❤️