Functions/Update-ESXiSSL.psm1

function Update-ESXiSSL {
<#
    .SYNOPSIS
    Function to replace SSL certificate of an ESXi host with CA signed certificate.
     
    .DESCRIPTION
    Function to replace SSL certificate of an ESXi host with CA signed certificate.
    Uses:
    OpenSSL - http://slproweb.com/products/Win32OpenSSL.html
    certreq - built into Windows
    pscp - http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
     
    .PARAMETER VMHost
    VMHost to configure certificate for.
 
    .PARAMETER OpenSSLEXE
    Path to openssl.exe
 
    .PARAMETER PSCPLEXE
    Path to pscp.exe
 
    .PARAMETER CertPath
    Path to folder to store certificates in
 
    .PARAMETER CAName
    Name of Certification Authority, e.g. ROOTCA01\ROOTCA01-CA
 
    .PARAMETER CertTemplate
    Name of the certificate template to use, e.g. CertificateTemplate:VMware-SSL
 
    .PARAMETER Organization
    Name of the Organization to use in the certificate
 
    .PARAMETER Locality
    Name of the Locality to use in the certificate
 
    .PARAMETER State
    Name of the State to use in the certificate
 
    .PARAMETER CountryName
    Name of the country to use in the certificate, e.g. US or GB
 
    .PARAMETER Credential
    A PS credential object.
 
    .INPUTS
    String.
    System.Management.Automation.PSObject.
 
    .OUTPUTS
    None.
 
    .EXAMPLE
    PS> Update-ESXiSSL -VMHost ESXi01 -OpenSSLEXE "C:\OpenSSL\bin\openssl.exe" -PSCPEXE "C:\Putty\pscp.exe" -CertPath "C:\vCenter\Certs" -CAName "ROOTCA01\ROOTCA01-CA" -CertTemplate "CertificateTemplate:VMware-SSL" -Organization "Duff" -Locality "Springfield" -State "Springfield County" -CountryName "US" -Credential (Get-Credential)
 
    .EXAMPLE
    PS> Get-VMHost ESXi01 | Update-ESXiSSL -OpenSSLEXE "C:\OpenSSL\bin\openssl.exe" -PSCPEXE "C:\Putty\pscp.exe" -CertPath "C:\vCenter\Certs" -CAName "ROOTCA01\ROOTCA01-CA" -CertTemplate "CertificateTemplate:VMware-SSL" -Organization "Duff" -Locality "Springfield" -State "Springfield County" -CountryName "US" -Credential (Get-Credential)
 
    .NOTES
    Adapted from http://blog.netnerds.net/2013/06/update-your-esxs-ssl-certs-with-your-own-windows-domain-ca-certificates-using-powercli/
 
#>

[CmdletBinding()]

    Param
    (

    [parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [ValidateNotNullOrEmpty()]
    [PSObject[]]$VMHost,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [IO.FileInfo]$OpenSSLEXE,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [IO.FileInfo]$PSCPEXE,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [IO.DirectoryInfo]$CertPath,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$CAName,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$CertTemplate,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$Organization,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$Locality,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$State,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [String]$CountryName,

    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [Management.Automation.PSCredential]$Credential
    )    

    begin {
    

        # --- Check that OpenSSL.exe is available
        if (!($OpenSSLEXE.Exists)){

            throw "Please supply the correct path to OpenSSL.exe"
        }

        # --- Check that pscp.exe is available
        if (!($PSCPEXE.Exists)){

            throw "Please supply the correct path to PSCP.exe"
        }

        # --- Create the path to store the certificates
        if (!(Test-Path -Path "$CertPath\ESXi")){

            New-Item -Path "$CertPath\ESXi" -ItemType Directory -Force
        }

        $ESXiUsername = $Credential.UserName.TrimStart('\')
        $ESXiPassword = $Credential.GetNetworkCredential().Password
    }

    process {

        try {

            foreach ($ESXiHost in $VMHost){

                if ($ESXiHost.GetType().Name -eq "string"){
                
                    try {
                        $ESXiHost = Get-VMHost $ESXiHost -ErrorAction Stop
                    }
                    catch [Exception]{
                        Write-Warning "VMHost $ESXiHost does not exist"
                    }
                }
                
                elseif ($ESXiHost -isnot [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]){
                    Write-Warning "You did not pass a string or a VMHost object"
                    Return
                }
    
                $DisableSSH = $false
                $HostName = $ESXiHost.ExtensionData.Config.Network.DnsConfig.HostName
                $DNSName = $HostName + "." + $ESXiHost.ExtensionData.Config.Network.DnsConfig.DomainName
                $ESXiSSLPath = "$ESXiUsername@$($DNSName):/etc/vmware/ssl"

                $RUICSR = "$CertPath\ESXi\$HostName\rui.csr"
                $TempRUIKey = "$CertPath\ESXi\$HostName\temprui.key"
                $ESXiCFG = "$CertPath\ESXi\$HostName\esxi.cfg"
                $RUIKey = "$CertPath\ESXi\$HostName\rui.key"
                $RUICRT = "$CertPath\ESXi\$HostName\rui.crt"
                $CertBackupFolder = "$CertPath\ESXi\$HostName\CertBackup"            

                if (!(Test-Path -Path "$CertPath\ESXi\$HostName")){

                    New-Item -Path "$CertPath\ESXi\$HostName" -ItemType Directory -Force | Out-Null
                }

                if (!(Test-Path -Path $CertBackupFolder)){

                    New-Item -Path $CertBackupFolder -ItemType Directory -Force | Out-Null
                }

                $ReqText = @"
[ req ]
default_bits = 2048
default_keyfile = rui.key
distinguished_name = req_distinguished_name
encrypt_key = no
prompt = no
string_mask = nombstr
req_extensions = v3_req
 
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment, dataEncipherment, nonRepudiation
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = DNS:$HostName, DNS:$DNSName
 
[ req_distinguished_name ]
countryName = $CountryName
stateOrProvinceName = $State
localityName = $Locality
0.organizationName = $Organization
commonName = $HostName
"@


                # --- Write the file with no additional blank line at the end
                [System.IO.File]::WriteAllText($ESXiCFG, $ReqText)

                # --- Create the certificate request rui.csr and temp private key
                Invoke-Expression "$OpenSSLEXE req -new -nodes -out $RUICSR -keyout $TempRUIKey -config $ESXiCFG" 2>&1

                # --- Convert the Key to be in RSA format
                Invoke-Expression "$OpenSSLEXE rsa -in $TempRUIKey -out $RUIKey" 2>&1

                # --- Submit the certificate request
                Invoke-Expression "certreq -submit -config $CAName -attrib $CertTemplate $RUICSR $RUICRT" 2>&1 | Out-Null

                # --- Enable SSH if necessary
                $SSHService = Get-VMHostService -VMHost $ESXIHost | Where-Object {$_.Key -eq "TSM-SSH"}

                if (!($SSHService.Running)){

                    $DisableSSH = $true
                    Start-VMHostService -HostService $SSHService -Confirm:$false | Out-Null
                }

                # --- Test SCP authentication with VMware host
                if (!($CheckAuth = Invoke-Expression "echo `"Y`" | $PSCPEXE -scp -pw $($ESXiPassword) -ls $($ESXiSSLPath)" 2>&1)){

                    throw "Unable to authenticate via SCP with $DNSName"
                }

                # --- Backup existing certs on the host
                Invoke-Expression "$PSCPEXE -scp -batch -pw $($ESXiPassword) `"$($ESXiSSLPath)/rui.key`" $CertBackupFolder" | Out-Null
                Invoke-Expression "$PSCPEXE -scp -batch -pw $($ESXiPassword) `"$($ESXiSSLPath)/rui.crt`" $CertBackupFolder" | Out-Null

                # --- Check backup was successful before continuing
                if(!((Test-Path "$CertBackupFolder\rui.key") -and (Test-Path "$CertBackupFolder\rui.crt"))){
    
                    throw "Cert backup was not successful for $DNSName"
                }

                # --- Upload new certs to the host
                Invoke-Expression "$PSCPEXE -scp -batch -pw $($ESXiPassword) $($RUIKey) $($ESXiSSLPath)" | Out-Null
                Invoke-Expression "$PSCPEXE -scp -batch -pw $($ESXiPassword) $($RUICRT) $($ESXiSSLPath)" | Out-Null


                # --- Disable SSH if enabled earlier
                if ($DisableSSH){

                    Stop-VMHostService -HostService $SSHService -Confirm:$false | Out-Null
                }

                # --- Restart the host to complete the switch over to the new certificate
                $ESXiHost | Restart-VMHost -Confirm:$false
            }
        }
        catch [Exception] {
            
            throw "Unable to update certificate on host"
        }
    }

    end {

    }
}