Generating And sending HTML reports in Email through PowerShell

Spread The Knowledge

Hi Everyone! Today we are back with an interesting PowerShell script which will first collect data, convert it to HTML, and then will send in the email. You can also say this an end-to-end automation that I implemented a few years ago to automate a time-consuming and tedious task. Let’s Start “Generating And Sending HTML Reports In Email Through PowerShell

powershell script to send email
PowerShell

Steps:

The example we will consider is generating and sending an HTML server’s report in the message body. There are three different tasks which we will consolidate in one script to automate it. Below are the tasks:

  • Collecting Data
  • Converting To HTML form
  • Sending the HTML report in Email Body

Data Collection:

As a first step, we need to collect data from all the servers in our environment. To accomplish that we will use the custom PowerShell object and then we will store the data in form of an Array.

First we will import the server names from a text file:

$nodes = Get-Content C:\Temp\servers.txt

Now it’s time to create a for loop and custom object to collect data from all the servers.

$report = @()
foreach( $node in $nodes) {
    
    $serverObject = "" | Select-Object ServerCount, ServerName, State, CTotalSpace, CFreeGB, CUtil, ETotalSpace, EFreeGB, EUtil, AutomaticServices, LastRebootedOn, Days, Hours, Minutes, Seconds

    $serverObject.ServerCount = $count
    $serverObject.ServerName = $node

    if (test-connection $node -count 1 -quiet)
    {
        $serverObject.State = "Active"

        Try{
        $cdisk = Invoke-Command -ComputerName $node  -ScriptBlock { Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" | Select-Object FreeSpace, Size } -ErrorAction SilentlyContinue
        $ctotal = [math]::Round($cdisk.Size/1gb,2)
        $serverObject.CTotalSpace =  "$ctotal GB"
        $cfree = [math]::Round($cdisk.FreeSpace/1gb,2)
        $serverObject.CFreeGB = "$cfree GB"
        $util = [math]::Round(($cfree*100)/$ctotal,2)
        $serverObject.CUtil = "$util%"
        }
        Catch{
        $serverObject.CTotalSpace = "WinRM-Issue"
        $serverObject.CFreeGB = "WinRM-Issue"
        $serverObject.CUtil = "WinRM-Issue"
        }
        
        Try{
        $edisk = Invoke-Command -ComputerName $node  -ScriptBlock { Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='E:'" | Select-Object FreeSpace, Size } -ErrorAction SilentlyContinue 
        $etotal = [math]::Round($edisk.Size/1gb,2)
        $serverObject.ETotalSpace =  "$etotal GB"

        $efree = [math]::Round($edisk.FreeSpace/1gb,2)
        $serverObject.EFreeGB = "$efree GB"
        $eutil = [math]::Round(($efree*100)/$etotal,2)
        $serverObject.EUtil = "$eutil%"
        }

        Catch{
        $serverObject.ETotalSpace = "WinRM-Issue"
        $serverObject.EFreeGB = "WinRM-Issue"
        $serverObject.EUtil = "WinRM-Issue"
        }

        Try{
        $stopped  = Invoke-Command -ComputerName $node -ScriptBlock { Get-Service | Where-Object {$_.StartType -eq "Automatic" -and $_.Status -eq "Stopped"} } -ErrorAction SilentlyContinue
        $no = $stopped.Count
        if($no -gt 0)
        {
            $serverObject.AutomaticServices = "$no Not Running"
          
        }
        else
        {
            $serverObject.AutomaticServices = "All Running"
        }}

        Catch{
        $serverObject.AutomaticServices = "WinRM-Issue"
        }

        Try{
        $time = Invoke-Command -ComputerName $node  -ScriptBlock { (Get-WmiObject win32_operatingsystem | select @{Label = 'LastBootUpTime'; Expression={$_.ConverttoDateTime($_.lastbootuptime)}}).LastBootUpTime } -ErrorAction SilentlyContinue
        $serverObject.LastRebootedOn = $time

        $days = ((Get-Date)-$time).Days
        $serverObject.Days = "$days Days"
            
        
        $hours = ((Get-Date)-$time).Hours
        $serverObject.Hours = "$hours Hours"

    
        $minutes = ((Get-Date)-$time).Minutes
        $serverObject.Minutes = "$minutes Minutes"

    
        $seconds = ((Get-Date)-$time).Seconds
        $serverObject.Seconds = "$seconds Seconds"
        }
        Catch{
        $serverObject.LastRebootedOn = "WinRM-Issue"
        $serverObject.Days = "WinRM-Issue"
        $serverObject.Minutes = "WinRM-Issue"
        $serverObject.Seconds = "WinRM-Issue"
        }
    }
    else
    {
        $serverObject.State = "Failed"
        $serverObject.ServerIp = "NA"

        $serverObject.CTotalSpace = "NA"
        $serverObject.CFreeGB = "NA"
        $serverObject.CUtil = "NA"

        $serverObject.ETotalSpace = "NA"
        $serverObject.EFreeGB = "NA"
        $serverObject.EUtil = "NA"
        $serverObject.AutomaticServices = "NA"

        $serverObject.LastRebootedOn = "NA"
        $serverObject.Days = "NA"
        $serverObject.Hours = "NA"
        $serverObject.Minutes = "NA"
        $serverObject.Seconds = "NA"
    }

     $report += $serverObject 
    
}

As you can see in the last line, data from all the servers have been captured in the $report variable. Hence our first task has been completed.

Formatting And Converting To HTML:

Now that we have all the data in an Array variable $report, it’s time to format this data and convert it to HTML format to get this ready to send in the email. To format the data, we will first store a little CSS code in the $Header variable.

$Header = @"
<style>
TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #BFBFBF;}
TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
</style>
"@

Let’s convert it to HTML:

$htmlData = $report | ConvertTo-Html -PreContent "<h1>Server Report</h1>" -Head $Header

Sending the HTML report in Email

Here come’s the mail step. Now we will embed this HTML data into the email body and send. For that, we will use the popular PowerShell cmdlet Send-MailMessage.

[string]$body = $FormattedHTML
$smtp = "smtp.domain.com"
$to = "techyguy26@gmail.com"
$from = "no-reply@automation.com"
$cc = "samandeep540@gmail.com"
$subject = "Server Report $(Get-Date)"
Send-MailMessage -SmtpServer $smtp -To $to -Cc $cc -From $from -Subject $subject -Body $body -BodyAsHtml

As you can see in the above code, you can define all the variables first for better readability. If you are not aware of your SMTP server or do not want to use the SMTP server, there is an alternate way also to send email in PowerShell without defining the SMTP server in the code.

Resultant Script:

Here is the complete script with all the three steps explained above:

Try
{
    $nodes = Get-Content C:\Temp\Sc\servers.csv

}

Catch
{
    Write-Error "File Path Error. Please Correct and Try Again"
    Write-Error $_.Exception
    Exit -1
}
$report = @()
foreach( $node in $nodes) {
    
    $serverObject = "" | Select-Object ServerCount, ServerName, State, CTotalSpace, CFreeGB, CUtil, ETotalSpace, EFreeGB, EUtil, AutomaticServices, LastRebootedOn, Days, Hours, Minutes, Seconds

    $serverObject.ServerCount = $count
    $serverObject.ServerName = $node

    if (test-connection $node -count 1 -quiet)
    {
        $serverObject.State = "Active"

        Try{
        $cdisk = Invoke-Command -ComputerName $node  -ScriptBlock { Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" | Select-Object FreeSpace, Size } -ErrorAction SilentlyContinue
        $ctotal = [math]::Round($cdisk.Size/1gb,2)
        $serverObject.CTotalSpace =  "$ctotal GB"
        $cfree = [math]::Round($cdisk.FreeSpace/1gb,2)
        $serverObject.CFreeGB = "$cfree GB"
        $util = [math]::Round(($cfree*100)/$ctotal,2)
        $serverObject.CUtil = "$util%"
        }
        Catch{
        $serverObject.CTotalSpace = "WinRM-Issue"
        $serverObject.CFreeGB = "WinRM-Issue"
        $serverObject.CUtil = "WinRM-Issue"
        }
        
        Try{
        $edisk = Invoke-Command -ComputerName $node  -ScriptBlock { Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='E:'" | Select-Object FreeSpace, Size } -ErrorAction SilentlyContinue 
        $etotal = [math]::Round($edisk.Size/1gb,2)
        $serverObject.ETotalSpace =  "$etotal GB"

        $efree = [math]::Round($edisk.FreeSpace/1gb,2)
        $serverObject.EFreeGB = "$efree GB"
        $eutil = [math]::Round(($efree*100)/$etotal,2)
        $serverObject.EUtil = "$eutil%"
        }

        Catch{
        $serverObject.ETotalSpace = "WinRM-Issue"
        $serverObject.EFreeGB = "WinRM-Issue"
        $serverObject.EUtil = "WinRM-Issue"
        }

        Try{
        $stopped  = Invoke-Command -ComputerName $node -ScriptBlock { Get-Service | Where-Object {$_.StartType -eq "Automatic" -and $_.Status -eq "Stopped"} } -ErrorAction SilentlyContinue
        $no = $stopped.Count
        if($no -gt 0)
        {
            $serverObject.AutomaticServices = "$no Not Running"
          
        }
        else
        {
            $serverObject.AutomaticServices = "All Running"
        }}

        Catch{
        $serverObject.AutomaticServices = "WinRM-Issue"
        }

        Try{
        $time = Invoke-Command -ComputerName $node  -ScriptBlock { (Get-WmiObject win32_operatingsystem | select @{Label = 'LastBootUpTime'; Expression={$_.ConverttoDateTime($_.lastbootuptime)}}).LastBootUpTime } -ErrorAction SilentlyContinue
        $serverObject.LastRebootedOn = $time

        $days = ((Get-Date)-$time).Days
        $serverObject.Days = "$days Days"
            
        
        $hours = ((Get-Date)-$time).Hours
        $serverObject.Hours = "$hours Hours"

    
        $minutes = ((Get-Date)-$time).Minutes
        $serverObject.Minutes = "$minutes Minutes"

    
        $seconds = ((Get-Date)-$time).Seconds
        $serverObject.Seconds = "$seconds Seconds"
        }
        Catch{
        $serverObject.LastRebootedOn = "WinRM-Issue"
        $serverObject.Days = "WinRM-Issue"
        $serverObject.Minutes = "WinRM-Issue"
        $serverObject.Seconds = "WinRM-Issue"
        }
    }
    else
    {
        $serverObject.State = "Failed"
        $serverObject.ServerIp = "NA"

        $serverObject.CTotalSpace = "NA"
        $serverObject.CFreeGB = "NA"
        $serverObject.CUtil = "NA"

        $serverObject.ETotalSpace = "NA"
        $serverObject.EFreeGB = "NA"
        $serverObject.EUtil = "NA"
        $serverObject.AutomaticServices = "NA"

        $serverObject.LastRebootedOn = "NA"
        $serverObject.Days = "NA"
        $serverObject.Hours = "NA"
        $serverObject.Minutes = "NA"
        $serverObject.Seconds = "NA"
    }

     $report += $serverObject
}

$Header = @"
<style>
TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #BFBFBF;}
TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;}
</style>
"@

$htmlData = $report | ConvertTo-Html -PreContent "<h1>Server Report</h1>" -Head $Header
$FormattedHTML =  $htmlData | ForEach {

   $_ -replace "<td>Active</td>","<td bgcolor='#40B070'>Pinging</td>"  -replace "<td>Failed</td>","<td bgcolor='#FF6666'>Failed</td>"  -replace "<td>All Running</td>","<td bgcolor='#40B070'>All Running</td>"  -replace "<td>WinRM-Issue</td>","<td bgcolor='#FADB52'>WinRM-Issue</td>"

}

[string]$body = $FormattedHTML
$smtp = "smtp.domain.com"
$to = "techyguy26@gmail.com"
$from = "no-reply@automation.com"
$cc = "samandeep540@gmail.com"
$subject = "Server Report Post Validation $(Get-Date)"
Send-MailMessage -SmtpServer $smtp -To $to -Cc $cc -From $from -Subject $subject -Body $body -BodyAsHtml

This is how we can automate the whole task in a single script. I hope it will useful for you too. Let me know in the comment section if you have also implemented similar automation in PowerShell.

Other Interesting Articles:


Spread The Knowledge

1 thought on “Generating And sending HTML reports in Email through PowerShell”

Leave a Comment