Help with emailing expiring SSL certificates

Below is a script I am using for generate a list of SSL certificates that are expiring in the next 90 days, and the script includes sending an email out with the said information. The problem I am having is if there is only one record it counts, it wont generate the list into html format, and therefore wont send an email out. But if I run in ISE, the certificates show up on screen.

NOTE: if I have more than one cert that i finds, it sends out the email without issue.

So I need help to get it to send out the email even if it’s just one certificate (right now it only sends email if there are more than one).

# Reads the current directory path from the location of this file
$currentDir = Split-Path $MyInvocation.MyCommand.Path

$PCName = $env:COMPUTERNAME

$DaysToExpiration = 90
 
$expirationDate = (Get-Date).AddDays($DaysToExpiration)
 
$certs = Get-ChildItem CERT:LocalMachine/My

$today = get-date -format G | out-string

$Today

$ExpiredBody = $certs | select Subject, @{Name="Expired"; Expression = {$_.NotAfter}}, Issuer | Sort-Object "Expired" | where { ($_.Expired -lt $Today) } 
$ExpiringBody = $certs | select Subject, @{Name="Expires"; Expression = {$_.NotAfter}}, Issuer | Sort-Object "Expires" | where { ($_.Expires -gt $Today) -and ($_.Expires -lt $expirationDate) } 

if ($ExpiringBody){
echo ""
echo "Listed below are Certificates that are about to expire in the next $DaysToExpiration days on $PCName" 
$ExpiringBody
}

if ($ExpiredBody){
echo ""
echo  "Listed below are Certificates that have expired on $PCName" 
$ExpiredBody
}

$a = "<style>"
$a = $a + "TABLE{width: 100%;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
$a = $a + "TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}"
$a = $a + "TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}"
$a = $a + "</style>"

$ExtendExpiredBody = $ExpiredBody | ConvertTo-HTML
$ExtendExpiringBody = $ExpiringBody | ConvertTo-HTML  -head $a

$EmailFrom = "$env:COMPUTERNAME@bsee.gov"
$EmailTo = "myemail@domain.com"

$EmailExpiringBody = "Listed below are Certificates that are about to expire in the next $DaysToExpiration days on $PCName" 
if ($ExpiredBody){
$EmailExpiredBody = "<br> Listed below are Certificates that have expired on $PCName" 
}
$EmailBody = $EmailExpiringBody + $ExtendExpiringBody + $EmailExpiredBody + $ExtendExpiredBody

$EmailBody = $EmailBody + ("<br>Report last updated on {0}.<br>Script Hosted on server {3}.<br>Script Path: {4}" -f (Get-Date -displayhint date),$env:userdomain,$env:username,$env:COMPUTERNAME,$currentDir) 

$EmailSubject = "Certificates that are Expired or Expiring within the next $DaysToExpiration days on $PCName"
$SMTPServer = "mailserver.net"
 
if ($ExpiringBody.Length -ge 1 -or $ExpiredBody.Length -ge 1)
{
  Write-host "Sending Email"
  Send-MailMessage -From $EmailFrom -To $EmailTo -Subject $EmailSubject -BodyAsHtml $EmailBody -SmtpServer $SMTPServer
  $EmailBody | Out-File C:\bin\Certs.htm
}

If you have a command retrurning more than 1 element PowerShell implicitly creates an array for you containing the returned objects. This array provides a property with the name of β€œCount” or β€œLength”. If the same command returns only 1 element you get only this element in return - no array - so no property β€œcount” and - depending on the return object - probably no property β€œlength”. But even if you get a property length it might not be what you expected.

To circumvent this behaviour and force PowerShell to create arrays for certain outputs you have to cast the return of the command to an array with @().

The following code worked for me:

$DaysToExpiration = 90
$expirationDate   = (Get-Date).AddDays($DaysToExpiration)
$currentDir       = Split-Path $MyInvocation.MyCommand.Path
$certs            = Get-ChildItem CERT:LocalMachine/My
$today            = (Get-Date).Date
$SMTPServer       = 'mailserver.net'
$EmailTo          = 'myemail@domain.com'
$EmailFrom        = "$env:COMPUTERNAME@bsee.gov"

$ExpiredBody = @(
    $certs |
        Where-Object { ($_.NotAfter -lt $Today) } |
            Sort-Object NotAfter |
                Select-Object -Property Subject, NotAfter, Issuer
)
$ExpiringBody = @(
    $certs |
        Where-Object { ($_.NotAfter -gt $Today) -and ($_.NotAfter -lt $expirationDate) } |
            Sort-Object -Property NotAfter |
                Select-Object -Property Subject, NotAfter, Issuer
)

$head = @'
<style>
TABLE{width: 100%;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}
TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}
</style>
'@

$ExtendExpiredBody = $ExpiredBody | ConvertTo-HTML
$ExtendExpiringBody = $ExpiringBody | ConvertTo-HTML  -head $head

$EmailExpiringBody = "Listed below are Certificates that are about to expire in the next $DaysToExpiration days on $($env:COMPUTERNAME)" 
if ($ExpiredBody) {
    $EmailExpiredBody = "<br> Listed below are Certificates that have expired on $($env:COMPUTERNAME)" 
}
$EmailBody = $EmailExpiringBody + $ExtendExpiringBody + $EmailExpiredBody + $ExtendExpiredBody
$EmailBody = $EmailBody + ("<br>Report last updated on {0}.<br>Script Hosted on server {3}.<br>Script Path: {4}" -f $today.ToLongDateString(), $env:userdomain, $env:username, $env:COMPUTERNAME, $currentDir) 
$EmailSubject = "Certificates that are Expired or Expiring within the next $DaysToExpiration days on $($env:COMPUTERNAME)"
 
if ($ExpiringBody.Count -ge 1 -or $ExpiredBody.Count -ge 1) {
    $SendMailMessageProps = @{
        From       = $EmailFrom
        To         = $EmailTo
        Subject    = $EmailSubject
        Body       = $EmailBody
        BodyAsHtml = $true
        SmtpServer = $SMTPServer
    }
    Send-MailMessage @SendMailMessageProps
    $EmailBody | Out-File C:\bin\Certs.htm
}

I re-arranged, corrected and streamlined your code a little bit. :wink:

2 Likes

Nice! that worked perfectly! Thank you!