Creating self signed certificates programmatically from 32bit PowerShell

I’ve been working with the Sitecore Habitat team to incorporate Sitecore Commerce into the Habitat implementation of Sitecore’s Helix principles. Part of that work saw me contributing some PowerShell scripts to support developer workflows with Sitecore Commerce. There was a lot more contributed than just this one function but this was one area I found had very little complete examples on the web so thought it best to blog about so other can benefit.

Some of the challenges were creating a function that work in both 32bit and 64bit PowerShell exe’s and also not have a reliance on any downloads.

Anyway here it is:


function New-Certificate
{
    param 
    (
        [Parameter(Mandatory=$True)][PSCustomObject]$certificateSettingList,
        [Parameter(Mandatory=$True)][PSCustomObject]$installFolderSetting
    )
    begin 
    {
        Write-Verbose "Setting Up Certificates"
    }
    process
    {
        $myCertStoreLocation = "cert:LocalMachineMy"
        $rootCertStoreLocation = "cert:LocalMachineRoot"
        $protocol = "https"
        $port = "443"

        Foreach ($certificateSetting in $certificateSettingList)
        {
            Write-Verbose "Setting up certificate $($certificateSetting.dnsName)"
            
            $certificate = Get-ChildItem $myCertStoreLocation | where-object { $_.DnsNameList -like $certificateSetting.dnsName }  | Select-Object -First 1
            If(-not $certificate)
            {
                Write-Verbose "Creating certificate $($certificateSetting.dnsName) in store '$myCertStoreLocation'"
                
                # The self signed certificate command only supports 'Cert:CurrentUserMy' or 'Cert:LocalMachineMy'                    
                PKINew-SelfSignedCertificate -DnsName $certificateSetting.dnsName -CertStoreLocation $myCertStoreLocation -verbose | Write-Verbose
                $certificate = Get-ChildItem $myCertStoreLocation | where-object { $_.DnsNameList -like $certificateSetting.dnsName }  | Select-Object -First 1
      
                If(-not $certificate)
                {
                    Write-Host "Error, unable to create certificate $($certificateSetting.dnsName) in '$myCertStoreLocation'" -ForegroundColor red
                    return 1; 
                }

            }
            Else
            {
                Write-Verbose "Certificate $($certificateSetting.dnsName) already exists in store '$myCertStoreLocation'"
            }

            $rootCertificate = Get-ChildItem $rootCertStoreLocation | where-object { $_.DnsNameList -like $certificateSetting.dnsName }  | Select-Object -First 1
            If(-not $rootCertificate)
            {
                Write-Verbose "Creating certificate $($certificateSetting.dnsName) in store '$rootCertStoreLocation'"
                
                $mypwd = ConvertTo-SecureString -String "sitecore" -Force -AsPlainText
                $filePath = $installFolderSetting.path + "" + $certificateSetting.DnsName + ".pfx"

                Write-Verbose "Exporting certificate to $($installFolderSetting.path)"
                $certificate | Export-PfxCertificate -FilePath $filePath -Password $mypwd -Verbose | Write-Verbose

                Write-Verbose "Importing certificate $($installFolderSetting.path) to store '$rootCertStoreLocation'"
                Import-PfxCertificate -FilePath $filePath -CertStoreLocation $rootCertStoreLocation -Password $mypwd -Verbose | Write-Verbose

                $rootCertificate = Get-ChildItem $rootCertStoreLocation | where-object { $_.DnsNameList -like $certificateSetting.dnsName }  | Select-Object -First 1
                If(-not $rootCertificate)
                {
                    Write-Host "Error, unable to create certificate $($certificateSetting.dnsName) in '$rootCertStoreLocation'" -ForegroundColor red
                    return 1; 
                }
            }
            Else
            {
                Write-Verbose "Certificate $($certificateSetting.dnsName) already exists in store '$rootCertStoreLocation'"
            }

            # AppId is used as a reference to which application created the binding for audit purposes.
            $applicationId = ([guid]::newguid()).ToString('B')
            
            # From IIS 7, the OS is responsible for SSL to port mappings. The OS doesn't care about what IIS site made the configuration - thats just a visualisation 
            # in IIS Manager (i.e. the link in IIS to the SSLPort mapping is maintained in IIS metadata). 
            # NOTE: there is potentialy you can have a certificate mapped correctly and not see it in IIS Manager.
            $cmd = netsh http add sslcert hostnameport=$($certificateSetting.dnsName):$port certhash=$($certificate.Thumbprint) appid=$applicationId certstore=My

            if($cmd -Match 'SSL Certificate successfully added')
            {
                Write-Verbose "Certificate successfully bound to host '$($certificateSetting.dnsName)' and port '$port'."
            }
            ElseIf($cmd -Match 'Cannot create a file when that file already exists')
            {
                Write-Verbose "Certificate already bound. Response from command is: '$cmd'"
            }
            Else
            {
                Write-Host "Error, unable to bind certificate to hostname '$($certificateSetting.dnsName)'. Response from command '$cmd'." -ForegroundColor red
                return 1; 
            }

            # Change the IIS metadata so we can see the SSLPort configuration in IIS Manager
            $bindingInformation = "*:$($port):$($certificateSetting.dnsName)"
            $escaped = [Regex]::Escape($bindingInformation)
            $cmd = invoke-expression "$($env:WINDIR)system32inetsrvAppcmd list site `"$($certificateSetting.siteName)`" /Config"
            If($cmd -Match $escaped)
            {
                Write-Verbose "IIS configuration allready applied."
            }
            Else
            {
                $cmd = invoke-expression "$($env:WINDIR)system32inetsrvAppcmd set site /site.name: `"$($certificateSetting.siteName)`" /+`"bindings.[protocol='https',bindingInformation='$bindingInformation',sslFlags='0']`" /commit:apphost"
                if($cmd -Like "SITE object * changed")
                {
                    Write-Verbose "IIS  successfully bound to host '$($certificateSetting.dnsName)' and port '$port'."
                }
                Else
                {
                    Write-Host "Error, unable to create IIS metadata for binding of certificate '$($certificateSetting.dnsName)'. Response from command '$cmd'." -ForegroundColor red
                    return 1; 
                }
            }
        }

        return 0;
    }
    end { }  
}