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“

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:
- PowerShell Script to create a fake BSOD blue screen of death in windows
- 500 Internal Server Error – How To Troubleshoot and Fix
- Windows Command Line Utilities Every Professional Should Know
- How To Check System Uptime in Both Windows and Linux Machine
- Windows Server Administrator Scenario Based Interview Questions And Answers