How troubleshoot blank email body?

I received an email but the body is just blank. There is no text or message. Below is my code.

$servers = @("Server1","Server2","Server1","Server3")


##### Function to send mail #####
function sendMail([string]$msgbody) {
    $smtpServer = "SMTP.myDomain.com" 
    $mailer = new-object Net.Mail.SMTPclient($smtpserver)
    $From = "noreply@myDomain.com"
    $To = "myName@myDomain.com" 
    $subject = "SSL Certificate Expiring Soon" 
    $msg = new-object Net.Mail.MailMessage($from,$to,$subject,$msgbody)   
    $msg.IsBodyHTML = $true 
    $mailer.send($msg)
}


##### Loop through all servers in the server array #####
foreach($server in $servers) {

    try{
    
        $expiredCert += Invoke-Command -ComputerName $server -ScriptBlock {Get-ChildItem -Path Cert:\localmachine\my | ?{$_.NotAfter -lt (get-date).AddDays(30)} | Select Thumbprint,FriendlyName,NotAfter, NotBefore}

    } catch {
        $ErrorMessage = $_.Exception.Message
        $currentTime = Get-Date
        sendMail("There was a problem connecting to and/or querying "+ $server +" for the failed Server login attempts. Specific error:`n`n" + $ErrorMessage + "`n`n Timestamp: " + $currentTime + "")
    }

}

if ($expiredCert){
    sendMail($expiredCert)
    Write-Output $expiredCert
}

The Write-Output does show the value of the $expiredCert variable just not the email body that I got in my inbox. Any help is much appreciated.

The main item is that you are appending to a variable $expiredCert (e.g. +=), but do not see you setting the variable in the code (e.g. $expiredCert = @()). Also, you are sending a HTML email, but you are not converting the object to HTML, so if the variable would work, you would get a email body like SYSTEM.OBJECT.

Regardless, that isn’t the best method to really roll up objects into a variable. Additionally, would recommend a different approach. Regardless of the scenario, an error, certs meet criteria and exist or if there are no certs expiring, that is valuable data to capture to make sure all bases are covered. If a standard object schema is used, we can capture all results in a standard format to perform queries against or send an email. Here is a sample, but first you should test that you are getting back results from all server, all scenarios and then work on the email portion. There is also Send-MailMessage that is already a cmdlet rather than using the Net.Mail.Message directly.

$servers = @("Server1","Server2","Server1","Server3")

##### Loop through all servers in the server array #####
$results = foreach($server in $servers) {

    try{ 
  
        Invoke-Command -ComputerName $server  -ErrorAction Stop -ScriptBlock {
            $results = Get-ChildItem -Path Cert:\localmachine\my | 
                Where-Object -FilterScript {$_.NotAfter -lt (get-date).AddDays(30)} | 
                    Select-Object -Property @{Name='Server';Expression={$env:ComputerName}},
                                            Thumbprint,
                                            FriendlyName,
                                            NotAfter,
                                            NotBefore, 
                                            @{Name='Status';Expression={'Success'}}

            if (!$results) {
                [PSCustomObject]@{
                    Server       = $env:ComputerName
                    ThumbPrint   = $null
                    FriendlyName = $null
                    NotAfter     = $null
                    NotBefore    = $null
                    Status       = "Success - No Expiring Certs Found."
                }
            }
            else {
                $results
            }
        }

    } catch {
        [PSCustomObject]@{
            Server       = $server
            ThumbPrint   = $null
            FriendlyName = $null
            NotAfter     = $null
            NotBefore    = $null
            Status       = "There was a problem connecting to and/or querying {0} for the failed Server login attempts. <br><br>Specific error: {1} <br><br>Timestamp: {2}" -f $server, $_, (Get-Date)
        }
    }

}

$results

$sendMailMessageSplat = @{
    To         = "myName@myDomain.com"
    From       = "noreply@myDomain.com"
    Subject    = "SSL Certificate Expiring Soon"
    SmtpServer = "SMTP.myDomain.com"
    Body       = ($results | ConvertTo-Html) | Out-String
    BodyAsHtml = $true
}

Send-MailMessage @sendMailMessageSplat

Thank you so much for help, much appreciated! The code and email works just fine. I got the code to show the following but couldn’t figure out how to filter out the PSComputerName and PSShowComputerName properties. I don’t need to include these two columns or properties in my report.

This is the code.

Invoke-Command -ComputerName $server  -ErrorAction Stop -ScriptBlock {
            $results = Get-ChildItem -Path Cert:\localmachine\my | 
                Where-Object -FilterScript {$_.NotAfter -lt (get-date).AddDays(30)} | 
                    Select-Object -Property @{Name='Server';Expression={$env:ComputerName}},
                                            FriendlyName,
                                            Thumbprint,
                                            NotAfter

This is what I have.

$servers = @("database1")

##### Loop through all servers in the server array #####
$reports = foreach ($server in $servers) {          # changed name to avoid confusion with the $results variable inside the Invoke-Command scriptblock
    try { 
        Invoke-Command -ComputerName $server  -ErrorAction Stop -ScriptBlock {
            $results = Get-ChildItem -Path Cert:\localmachine\my | 
                            Where-Object -FilterScript { $_.NotAfter -lt (Get-Date).AddDays(30) } |
                                Select-Object -Property @{n='Server';e={$Using:Server}},
                                                        FriendlyName,
                                                        NotAfter,
                                                        Thumbprint
                                                        #@{n='Status';e={'Expiring Certs Found.'}}

            if (!$results) {
                $dataReturned = [PSCustomObject]@{
                                Server       = $Using:Server
                                FriendlyName = $null
                                NotAfter     = $null
                                ThumbPrint   = $null
                                #Status       = "Success - No Expiring Certs Found."
                            } 
                #$dataReturned.GetType()
                $dataReturned.PSObject.Properties.Remove("RunspaceId")
                $dataReturned.PSObject.Properties.Remove("PSComputerName")
                $dataReturned.PSObject.Properties.Remove("Status")
                $dataReturned.PSObject.Properties.Remove("PSShowComputerName")
            }
            $results    # send this back to the invoking machine and placed into the $reports variable outside the scriptblock
        }
    }
    catch {
        [PSCustomObject]@{
            Server       = $server
            FriendlyName = $null
            NotAfter     = $null
            ThumbPrint   = $null
            Status       = "There was a problem connecting to and/or querying {0} for the failed Server login attempts. <br><br>Specific error: {1} <br><br>Timestamp: {2}" -f $server, $_, (Get-Date)
        }
    }
}
$reports

I tried to remove those four pair values from the custom object but I don’t think I did it right because it’s not removing from the $reports output.