Jump to page sections
Here's what you need to, as of 2018-03-26, parse the Linux utility openssl's output about a certificate's expiration date (not after / notAfter) and "not before" date, and turn it into .NET DateTime objects in PowerShell.

I use my own SSHSessions module, in version 2.1.3, for access to a Linux server, in the examples. That module requires PowerShell version 3, but you can use the SSH-Sessions v2 module and then in the code use New-Object -TypeName PSObject -Property @{ ComputerName = $LinuxServer; ... } ... etc. instead of [PSCustomObject], to make it compatible with PowerShell version 2. It's time to move on from v2 in most situations, I guess.

There could also be openssl floating around for Windows somewhere, but for now I went with the "old one", on the Linux platform, and the output that tool produces.

Adapt the code as necessary if your needs are different. Possibly your output could be in another format, but my code should/could still be a good help to guide you on how to parse it into proper DateTime objects.

Example screenshot


Example openssl output about an expiring certificate

Here I connect to the Linux computer www.svendsentech.no and check the certificate for this site, www.powershelladmin.com, using a command I googled a while back (lost reference, sorry). It supports SNI as well with the -servername parameter (this was not in the original reference, I found out later). This can be used against load balancers too.
PS C:\temp> Import-Module SSHSessions
PS C:\temp> New-SshSession -ComputerName $LinuxServer -Credential $SSHCredentials

PS C:\temp> $SSHOutput = Invoke-SSHCommand -ComputerName $LinuxServer -Quiet `
    -Command "echo | openssl s_client -connect $($TargetCertDNS):443 -servername $($TargetCertDNS):443 2> /dev/null | openssl x509 -noout -dates"

PS C:\temp> $SSHOutput[0].Result
notBefore=Aug 30 01:01:00 2017 GMT
notAfter=Aug 30 01:01:00 2019 GMT

Then we need to parse that output to get nicely formatted .NET DateTime objects, which is not 100 % straight-forward, but I did the dirty work for us.

Complete code example

This is not a pre-written function, but code you can review and adapt yourself.
$LinuxServer = "www.svendsentech.no"
$TargetCertDNS = "www.powershelladmin.com"

#$SSHCredentials = Import-Clixml C:\Temp\testcreds.xml # protected by DPAPI...
#$SSHCredentials = Get-Credential root # set earlier

# Assuming SSHSessions module version above 1.9,
# otherwise don't index into the ".Result" property
# and there won't be an ".Error" property...
Import-Module SSHSessions -ErrorAction Stop
New-SshSession -ComputerName $LinuxServer -ErrorAction Stop -Credential $SSHCredentials

$SSHOutput = Invoke-SSHCommand -ComputerName $LinuxServer -Quiet `
    -Command "echo | openssl s_client -connect $($TargetCertDNS):443 -servername $($TargetCertDNS):443 2> /dev/null | openssl x509 -noout -dates"
if ($SSHOutput.Error) {
    # handle error
}
elseif ($SSHOutput[0].Result -match '^(?ms)\s*notBefore\s*=\s*(.+)notAfter\s*=\s*(.+)$') {
    Write-Verbose -Verbose "Regex matched."
    Write-Verbose -Verbose "`$Matches[1] (notBefore) is: $($Matches[1].TrimEnd())"
    Write-Verbose -Verbose "`$Matches[2] (notAfter) is:  $($Matches[2].TrimEnd())"
    $NotAfter, $NotBefore = "Unset", "Unset"
    $ErrorActionPreference = "Stop"
    try {
        $NotAfter = [DateTime]::ParseExact(
            [regex]::Replace(
                ($Matches[2] -replace '\s*GMT\s*$' -replace '(.+)\s+([\d:]+)\s+(\d{4})', '$1 $3 $2'), '(\w+)\s+(\d?\d)\s+(.+)', {
                    $args[0].Groups[1].Value + " " + ("{0:D2}" -f [int] $args[0].Groups[2].Value) + " " + $args[0].Groups[3].Value
                }
            ), 'MMM dd yyyy HH:mm:ss', [CultureInfo]::InvariantCulture)
    }
    catch {
        $NotAfter = "Parse error"
    }
    try {
        $NotBefore = [DateTime]::ParseExact(
            [regex]::Replace(
                ($Matches[1] -replace '\s+GMT\s*' -replace '^(.+)\s+([\d:]+)\s+(\d{4})$', '$1 $3 $2'), '(\w+)\s+(\d?\d)\s+(.+)', {
                    $args[0].Groups[1].Value + " " + ("{0:D2}" -f [int] $args[0].Groups[2].Value) + " " + $args[0].Groups[3].Value
                }
            ), 'MMM dd yyyy HH:mm:ss', [CultureInfo]::InvariantCulture)
    }
    catch {
            $NotBefore = "Parse error"
    }
    $ErrorActionPreference = "Continue"
}
else {
    $NotAfter = "Parse error (no match)"
    $NotBefore = "Parse error (no match)"
}

[PSCustomObject] @{
    ComputerName = $LinuxServer
    TargetCertificateDNS = $TargetCertDNS
    NotAfter = $NotAfter
    NotBefore = $NotBefore
}

How many days until the certificate expires?

Now let's find out how many days are left until this certificate expires. This could be used to trigger warnings, etc. PS C:\temp> $CertTemp = .\openssl-cert-temp.ps1 VERBOSE: Regex matched. VERBOSE: $Matches[1] (notBefore) is: Aug 30 01:01:00 2017 GMT VERBOSE: $Matches[2] (notAfter) is: Aug 30 01:01:00 2019 GMT PS C:\temp> $DaysLeft = [Math]::Round(($CertTemp.NotAfter - (Get-Date)).TotalDays, 1) PS C:\temp> $DaysLeft 521.1

And we see that there are now 521.1 days left until the certificate expires. A negative number means the certificate has already expired.

Powershell      Windows      .NET      openssl      Regex         All Categories

Google custom search of this website only

Minimum cookies is the standard setting. This website uses Google Analytics and Google Ads, and these products may set cookies. By continuing to use this website, you accept this.

If you want to reward my efforts