0

I have searched in vain to find a solution to this specific query but cannot find a situation identical to mine.

In IIS 8.5, say I have multiple domains, and I have a SAN SSL cert (not wildcard) bound to each one using SNI:

a.domain.com
b.domain.com
c.domain.com

If I wanted to add d.domain.com and generate a new SAN that includes the new domain, I want to be able to replace the current cert without having to re-bind the new one to the 3 above domains (I can then manually bind the new 4th domain).

Now imagine in my example above that I actually have 20 domains - it's fairly time-consuming to do this, especially if you add a new site every couple of weeks - not to mention the downtime whilst I re-bind the SSL site-per-site.

Is there a solution I can apply to automate this process? I could envisage a PS script doing it provided I have the new cert's hash, but my PS-fu is not strong enough to work out how to iterate through all the sites and re-apply the cert (if that's how it needs to be done). Ideally it would be a solution that automatically imports the new cert (.pfx), removes the old one, and rebinds the sites.

EDIT: To confirm, I'm using one IP address for all sites.

  • And for further confirmation, you are using a single IP for all the sites? – Hames Dec 13 '17 at 20:25
  • yes I am - will add to the question! – benpage Dec 14 '17 at 3:45
1

Copy and paste the following functions into your PowerShell window:

function Get-IisSslBinding{
    [CmdletBinding()]
    Param(
        [Parameter(Position=0)] [Alias("fi","sn")]
        [string]$FilterBySiteName,
        [Parameter(Position=1, ValueFromPipelineByPropertyName=$true)] [Alias("co")] [ValidateNotnullOrEmpty()]
        [string[]]$ComputerName=$env:ComputerName
    )
    Begin{
        Write-Verbose ("$(Get-Date) - INFO - Load Microsoft.Web.Administration assembly...")
        $null=[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Administration")
    }
    Process{
        Foreach($computer in $ComputerName){
            Try{
                If($computer -eq "$env:ComputerName"){
                    Write-Verbose ("$(Get-Date) - INFO - Open connection to local computer [ {0} ]..." -f $computer)
                    $webServer=New-Object Microsoft.Web.Administration.ServerManager
                    $null=$webServer
                }
                Else{
                    Write-Verbose ("$(Get-Date) - INFO - Open connection to remote computer [ {0} ]..." -f $computer)
                    $webServer=[Microsoft.Web.Administration.ServerManager]::OpenRemote($computer)
                }
                # filter sites
                $sites=($webServer.Sites | Where{$_.Name -match $FilterBySiteName})
                Foreach($site in $sites){
                    Write-Verbose ("$(Get-Date) - INFO - Get binding(s) for [ {0} ]..." -f $site.Name)
                    # filter bindings
                    $siteHttpsBindings=($site.Bindings | Where{$_.Protocol -eq "https"})
                    Foreach($siteHttpsBinding in $siteHttpsBindings){
                        Write-Verbose ("$(Get-Date) - INFO - Get binding information ...")
                        New-Object -Type PSObject -Property @{
                            'ComputerName'=$computer.ToUpper()
                            'SiteId'=$site.ID 
                            'SiteName'=$site.Name
                            'BindingInformation'=$siteHttpsBinding.GetAttributeValue("bindinginformation")
                            'Thumbprint'=$siteHttpsBinding.GetAttributeValue("certificateHash")
                            'CertificateStore'=$siteHttpsBinding.GetAttributeValue("certificateStoreName")
                            'Protocol'=$siteHttpsBinding.GetAttributeValue("protocol")
                        }
                    }
                }
            }
            Catch{
                Write-Verbose ("$(Get-Date) - ERROR - {0}" -f $_.Exception.GetBaseException().Message)
            }
            Finally{
                Write-Verbose ("$(Get-Date) - INFO - Dispose web server resources...")
                $webServer.Dispose()
            }
        }
    }
    End{
        Write-Verbose ("$(Get-Date) - INFO - Done")
    }
}
##
function Set-IisSslBinding{
    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Alias("oh")] [ValidateNotnullOrEmpty()]
        [string]$Thumbprint,
        [Parameter(Position=1, Mandatory=$true)] [Alias("nh")] [ValidateNotnullOrEmpty()]
        [string]$AfterThumbprint,
        [Parameter(Position=2, Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [Alias("sn")] [ValidateNotnullOrEmpty()]
        $SiteName,
        [Parameter(Position=3, Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [Alias("co")] [ValidateNotnullOrEmpty()]
        [string[]]$ComputerName=$env:ComputerName
    )
    Begin{
        Write-Verbose ("$(Get-Date) - INFO - Load Microsoft.Web.Administration assembly...")
        $null=[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Administration")
    }
    Process{
        Foreach($computer in $ComputerName){
            Try{
                If($computer -eq "$env:ComputerName"){
                    Write-Verbose ("$(Get-Date) - INFO - Open connection to local computer [ {0} ]..." -f $computer)
                    $webServer=New-Object Microsoft.Web.Administration.ServerManager
                    $IsCertificateInStore=((Get-ChildItem -Path CERT:\LocalMachine\My) -match $AfterThumbprint)
                }
                Else{
                    Write-Verbose ("$(Get-Date) - INFO - Open connection to remote computer [ {0} ]..." -f $computer)
                    $webServer=[Microsoft.Web.Administration.ServerManager]::OpenRemote($computer)
                }
                # If(-not $IsCertificateInStore){
                    # Write-Verbose ("$(Get-Date) - INFO - The computer [ {0} ] does not contain the certificate [ {1} ]... " -f $computer,$AfterThumbprint)
                    # Break
                # }
                Write-Verbose ("$(Get-Date) - INFO - Filter sites...")
                $sites=($webServer.Sites|where{$_.Name -match $SiteName})
                Foreach($site in $sites){
                    #filter bindings
                    $siteHttpsBindings=($site.Bindings|where{$_.Protocol -eq "https"})
                    Foreach($siteHttpsBinding in $siteHttpsBindings){
                        Switch($siteHttpsBinding.GetAttributeValue("certificateHash")){
                            $Thumbprint{
                                Write-Verbose ("$(Get-Date) - INFO - Remove old certificate [ {0} ]... " -f $siteHttpsBinding.GetAttributeValue("certificateHash"))
                                $BindingMethod=$siteHttpsBinding.Methods["RemoveSslCertificate"]
                                $BindingMethodInstance=$BindingMethod.CreateInstance()
                                $BindingMethodInstance.Execute()
                                Write-Verbose ("$(Get-Date) - INFO - Add new certificate [ {0} ]..." -f $AfterThumbprint)
                                $BindingMethod=$siteHttpsBinding.Methods["AddSslCertificate"]
                                $BindingMethodInstance=$BindingMethod.CreateInstance()
                                $BindingMethodInstance.Input.SetAttributeValue("certificateHash", $AfterThumbprint)
                                $BindingMethodInstance.Input.SetAttributeValue("certificateStoreName", "My")
                                $BindingMethodInstance.Execute()
                                New-Object -Type PSObject -Property @{
                                    'ComputerName'=$computer.ToUpper()
                                    'SiteId'=$site.ID 
                                    'SiteName'=$site.Name
                                    'BindingInformation'=$siteHttpsBinding.GetAttributeValue("bindingInformation")
                                    'Thumbprint'=$siteHttpsBinding.GetAttributeValue("certificateHash")
                                    'PreviousThumbprint'=$Thumbprint
                                }
                            }
                            Default{
                                Write-Verbose ("$(Get-Date) - INFO - Could not get https binding(s) attribute for [ {0} ]" -f $site.Name)
                                break
                            }
                        }                
                    }
                }
            }
            Catch{
                Write-Verbose ("$(Get-Date) - ERROR - {0}" -f $_.Exception.GetBaseException().Message)
            }
            Finally{
                Write-Verbose ("$(Get-Date) - INFO - Dispose web server resources...")
                $webServer.Dispose()
            }
        }
    }
    End{
        Write-Verbose ("$(Get-Date) - INFO - Done.")
    }
}

Then execute:

  1. To list all the sites and their bindings:

Get-IisSslBinding

  1. To update all the sites and their SSL bindings:

Get-IisSslBinding | Set-IisSslBinding -AfterThumbprint AAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCC

** Ensure the new SSL certificate is already in the SSL store. Also the Get-IisSslBinding function as a -FilterBySiteName param so you can target the exact site(s) you might need to touch.

  • This is awesome! Thankyou! – benpage Jan 9 '18 at 22:40
  • Glad to assist. Also the Set-IisSslBinding function is not dependent on the Get-IisSslBinding function. It can be used like so too Set-IisSslBinding -Thumbprint AAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCC -AfterThumbprint XXXXXXXXXXXXXXXXXXXYYYYYYYYYYZZZZZZZZZZZ -SiteName "The Exact Site Name" . And if you have the site on multiple computers, you could add the parameter like so -ComputerName WEB-SERVER001,WEB-SERVER002 – Hames Jan 11 '18 at 16:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.