Assign IIS SSL Certificate to Binding with Host Header using PowerShell

28,040

Solution 1

Right now I'm using this approach, which does work:

$guid = [guid]::NewGuid().ToString("B")
netsh http add sslcert hostnameport=$Name.domain.com:443 certhash=b58e54ca68c94f93c134c5da00a388ab0642a648 certstorename=MY appid="$guid"

Solution 2

Here is how I was able to generate a self-signed certificate for the machine FQDN and Add the SSL Certificate and Binding.

$fqdn = "$((Get-WmiObject win32_computersystem).DNSHostName).$((Get-WmiObject win32_computersystem).Domain)" 
$cert=(Get-ChildItem cert:\LocalMachine\My | where-object { $_.Subject -match "CN=$fqdn" } | Select-Object -First 1) 
if ($cert  -eq $null) { 
$cert = New-SelfSignedCertificate -DnsName $fqdn -CertStoreLocation "Cert:\LocalMachine\My" 
} 
$binding = (Get-WebBinding -Name SiteNameHere | where-object {$_.protocol -eq "https"})
if($binding -ne $null) {
    Remove-WebBinding -Name SiteNameHere -Port 443 -Protocol "https" -HostHeader $fqdn
} 
New-WebBinding -Name SiteNameHere -Port 443 -Protocol https -HostHeader $fqdn 
(Get-WebBinding -Name SiteNameHere -Port 443 -Protocol "https" -HostHeader $fqdn).AddSslCertificate($cert.Thumbprint, "my")

Solution 3

Based on @ElanHasson's answer, I made this script which will make a self-signed TLS certificate and apply it to a website. It could be tidied a bit, but it works:

Clear-Host
$certificateDnsName = 'my.localcert.ssl' # a name you want to give to your certificate (can be anything you want for localhost)

$siteName = "Default Web Site" # the website to apply the bindings/cert to (top level, not an application underneath!).
$fqdn = ""                     #fully qualified domain name (empty, or e.g 'contoso.com')


# ----------------------------------------------------------------------------------------
# SSL CERTIFICATE CREATION
# ----------------------------------------------------------------------------------------

# create the ssl certificate that will expire in 2 years
$newCert = New-SelfSignedCertificate -DnsName $certificateDnsName -CertStoreLocation cert:\LocalMachine\My -NotAfter (Get-Date).AddYears(2)
"Certificate Details:`r`n`r`n $newCert"


# ----------------------------------------------------------------------------------------
# IIS BINDINGS
# ----------------------------------------------------------------------------------------


$webbindings = Get-WebBinding -Name $siteName
$webbindings


$hasSsl = $webbindings | Where-Object { $_.protocol -like "*https*" }

if($hasSsl)
{
    Write-Output "ERROR: An SSL certificate is already assigned. Please remove it manually before adding this certificate."
    Write-Output "Alternatively, you could just use that certificate (provided it's recent/secure)."
}
else
{
    "Applying TLS/SSL Certificate"
    New-WebBinding -Name $siteName -Port 443 -Protocol https -HostHeader $fqdn #could add -IPAddress here if needed (and for the get below)
    (Get-WebBinding -Name $siteName -Port 443 -Protocol "https" -HostHeader $fqdn).AddSslCertificate($newCert.Thumbprint, "my")

    "`r`n`r`nNew web bindings"
    $webbindings = Get-WebBinding -Name $siteName
    $webbindings
}


"`r`n`r`nTLS/SSL Assignment Complete"

With fqdn empty (and no -IPAddress assigned), it will give you this in IIS:

IIS Self Signed Certificate Binding

Solution 4

new-item : Cannot create a file when that file already exists

Dropping the 0.0.0.0!443 binding beforehand fixes this for me:

Get-Item IIS:\SslBindings\0.0.0.0!443 | Remove-Item

FYI: Here's a Powershell script I made to bulk assign a wildcard certificate to sites using SNI/SSL host headers without explicit IP bindings, and where the friendly name of the new certificate is like *.example.com 2019

#SCRIPT FOR ADMIN POWERSHELL TO BULK ASSIGN A WILDCARD SSL CERTIFICATE TO ONE OR MORE WEBSITES USING SSL HOST HEADERS WITHOUT EXPLICIT IPS


# —————————————————————————————
# User Configurable Variables:
# —————————————————————————————
$wildcardDomain="*.example.com";    # This string should be present in the friendly name of the new SSL certificate

$yearMatchingNewCert="2019";    # This string should be UNIQUELY present in the friendly name of the new SSL certificate


# Make the IIS: drive available
Import-Module WebAdministration;



# —————————————————————————————
# Auto-Determine the certificate store to use from the usual 'My' or 'WebHosting' locations
# —————————————————————————————
$certInWebHostingStore=dir Cert:\localmachine\WebHosting | where-Object {$_.subject -like "$wildcardDomain*"};
$certInPersonalStore=dir Cert:\localmachine\My | where-Object {$_.subject -like "$wildcardDomain*"};
if ($certInWebHostingStore) {$certStoreDir="WebHosting"} elseif ($certInPersonalStore) {$certStoreDir="My"} else {$certStoreDir=null};
$certStorePath="\localmachine\$certStoreDir";
echo "███ The NEW certificate is living in this store: $certStorePath";


# —————————————————————————————
# Get the Thumbprint of the NEW certificate
# —————————————————————————————
$certThumbHashNew=Get-ChildItem -Path Cert:$certStorePath | where-Object {$_.subject -like "$wildcardDomain*" -and $_.FriendlyName -like "*$yearMatchingNewCert*"} | Select-Object -ExpandProperty Thumbprint;
echo "███ The NEW certificate's thumbprint hash is: $certThumbHashNew"; # If this displays as empty then you have either not installed the certificate, it's not in the usual Certificate stores or the certificate friendly name doesn't match the pattern "*.example.com 2019" e.g. "*.example.com (2018-2021)"



# —————————————————————————————
# Display the existing bindings
# —————————————————————————————
#Dir IIS:\SslBindings;                      # Shows all bindings
#Dir IIS:\SslBindings\!443!*;               # Shows all port 443 bindings
Dir IIS:\SslBindings\!443!$wildcardDomain;  # Shows all bindings in use matching the wildcard certificate



# —————————————————————————————
# Remove the Existing Bindings
# —————————————————————————————
# NOTE: SNI settings are in the Registry if all else fails: HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\HTTP\Parameters\SslSniBindingInfo
Get-Item IIS:\SslBindings\!443!$wildcardDomain | Remove-Item;



# —————————————————————————————
# Add the New Bindings
# —————————————————————————————
Get-Item -Path "cert:$certStorePath\$certThumbHashNew" | New-Item -Path IIS:\SslBindings\!443!$wildcardDomain;



# —————————————————————————————
# The IIS Manager doesn't seem to update its GUI without this bit
# —————————————————————————————
#(Get-WebBinding -Port 443 -Protocol "https" -HostHeader $wildcardDomain).RemoveSslCertificate($certThumbHashNew, $certStoreDir);
(Get-WebBinding -Port 443 -Protocol "https" -HostHeader $wildcardDomain).AddSslCertificate($certThumbHashNew, $certStoreDir);

Weirdly, viewing the changes in PowerShell (listing the bindings) doesn't show the changes until you get a new console session, so close and re-open the Admin PowerShell window.

# —————————————————————————————
# User Configurable Variables:
# —————————————————————————————
$wildcardDomain="*.example.com";
# —————————————————————————————


# Make the IIS: drive available
Import-Module WebAdministration;


# —————————————————————————————
# Display the new bindings
# —————————————————————————————
Dir IIS:\SslBindings\!443!$wildcardDomain



# —————————————————————————————
# Troubleshooting
# —————————————————————————————
# If things go awry, the 0.0.0.0 address usually seems to be at fault, particularly if the error is "New-Item : Cannot create a file when that file already exists"
# To remove it follow these steps, then start over with the previous script again:
# View all the port 443 bindings, not just the ones matching our wilcard:
#Dir IIS:\SslBindings\!443!*
# If the 0.0.0.0 binding shows in the list then use this to drop it:
#Get-Item IIS:\SslBindings\0.0.0.0!443 | Remove-Item

References used in making this script:

Solution 5

I'm not familiar with IIS, but the error says that the binding(file) already exists, so you're not adding a SSL binding, you're updating one it seems. Try adding -Force to the New-Item command. If it works like with files, it should overwrite the existing binding. Like:

New-WebBinding -name $Name -Protocol https -HostHeader "$Name.domain.com" -Port 443 -SslFlags 1
$cert = Get-ChildItem -Path Cert:\LocalMachine\My | where-Object {$_.subject -like "*cloud.domain.com*"} | Select-Object -ExpandProperty Thumbprint
get-item -Path "cert:\localmachine\my\$cert" | new-item -path IIS:\SslBindings\0.0.0.0!443!$Name.domain.com -Force
Share:
28,040
MichelZ
Author by

MichelZ

CIO of a Software Company headquartered in Switzerland with a passion for technology, especially in C#, ASP.NET, .NET and (Windows-) System Administration / Automation See my LinkedIn Profile for more Information

Updated on July 09, 2022

Comments

  • MichelZ
    MichelZ almost 2 years

    I'm trying to assign a certificate to a HTTPS binding. Unfortunately, I get this error from PowerShell:

    new-item : Cannot create a file when that file already exists
    At line:3 char:56
    +         get-item -Path "cert:\localmachine\my\$cert" | new-item -path IIS:\SslBi ...
    +                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [New-Item], Win32Exception
        + FullyQualifiedErrorId : System.ComponentModel.Win32Exception,Microsoft.PowerShell.Commands.NewItemCommand
    

    My PowerShell which I execute is:

     New-WebBinding -name $Name -Protocol https -HostHeader "$Name.domain.com" -Port 443 -SslFlags 1
     $cert = Get-ChildItem -Path Cert:\LocalMachine\My | where-Object {$_.subject -like "*cloud.domain.com*"} | Select-Object -ExpandProperty Thumbprint
     get-item -Path "cert:\localmachine\my\$cert" | new-item -path IIS:\SslBindings\0.0.0.0!443!$Name.domain.com
    

    It seems to be able to find the certificate, but is not able to assign it to the created binding. The binding gets created with the right IP/Port/HostHeader, SNI is checked, but SSL Certificate is "Not selected"

    It all works fine from IIS Manager

    I have tried various instructions from SO and other sites, e.g.:
    http://technet.microsoft.com/en-us/magazine/jj871065.aspx
    Powershell IIS7 Snap in Assign SSL certificate to https binding
    Powershell - Add SSL binding using shared certificate

    Also, I have tried with

    IIS:\SslBindings\0.0.0.0!443!$Name.domain.com
    

    and

    IIS:\SslBindings\0.0.0.0!443
    

    The Certificate has a subject of cloud.domain.com, and multiple SAN attributes, e.g. for **.domain.com*, domain.com, **.seconddomain.com*, seconddomain.com, cloud.domain.com

    Edit:

    Right now I'm using this approach, which does work:

    $guid = [guid]::NewGuid().ToString("B")
    netsh http add sslcert hostnameport=$Name.domain.com:443 certhash=b58e54ca68c94f93c134c5da00a388ab0642a648 certstorename=MY appid="$guid"
    

    I'm still interested however in a solution without netsh / appcmd

  • MichelZ
    MichelZ about 10 years
    Thanks. I am not overwriting an existing binding. Neither Get-Webbinding nor get-item -path IIS:\sslbindings\* show an existing binding for 0.0.0.0:443:mysite.domain.com. There is one for 0.0.0.0:443. I managed to do it using netsh, but I'd still be interested in the PowerShell way.
  • MichelZ
    MichelZ about 10 years
    And the -force parameter does not work, I get the same error
  • Frode F.
    Frode F. about 10 years
    Unfortunately I don't have much experience with IIS. It may look like the IIS provider in powershell isn't SNI-aware at this point. You could also consider a SNI with CCS solution? Isn't this a more maintenance-friendly solution anyways? :)
  • MichelZ
    MichelZ about 10 years
    I haven't played with Central Certificates yet, maybe worth a try. However, even without SNI, you should be able to assign the same certificate to multiple host headers. (i'm only using one cert currently), and SSL host headers have been around since IIS 7.. (so I would assume PS support for it)
  • CarlR
    CarlR over 8 years
    There is no truth in this, the certificate absolutely does not have to be marked as exportable and doing so it not advisable from a security perspective.
  • ouah
    ouah about 8 years
    How exactly does this help? This creates the HTTP.SYS binding but how does this help with your original problem of getting past the IIS binding error? I tried this and I still get the Cannot create File when that file exists. Are you associating the GUID somehow with the binding?
  • user3103701
    user3103701 almost 8 years
    I successfully use powershell to bind to certificates that are NOT marked as exportable. The OPs problem was binding ssl with a host header that does not work for me, but it has nothing to do with the exportablity of the certificate
  • mhenry1384
    mhenry1384 over 7 years
    You will have to do a "netsh http delete sslcert hostnameport=MYHOSTNAME :443" first if you want to change a binding that already exists.
  • theyetiman
    theyetiman over 5 years
    +1 for this part, which has been eluding me, but makes so much more sense than anything else I've read: New-WebBinding <foo>; (Get-WebBinding <foo>).AddSslCertificate(...)