HTML E-Mail Report Generator

Overview

I created this series of functions to allow you to conveniently take one or more PowerShell object and have it turn into an email report. For example, if you had a list of computers you have come out of a PowerShell script you wrote, these functions could help you turn them into an email with the same data without much effort.

Create an Email with A Simple Array of Data: EASY

Import-Module \\scriptserver\scripts\Get-DGMEmailReport\Get-DGMEmailReport.psm1 -Force

Import-Module \\pathtoscriptfile\Get-DGMEmailReport.psm1 -Force


$myarray = (Get-ADComputer -properties Description -filter {description -like "*sql*"} | Select Name,Description)

#Single Array Email Report
Get-DGMEmailReport `
    -Array $myarray `
    -ArrayTitle "SQL Servers" `
    -ArrayTitleColor "Blue" `
    -ArrayMessage "These are all the SQL Servers" `
    -ReportTitle "SQL Report 2017" `
    -from "SQLReports@emailaddress.com" `
    -To "c-dmaiolo@emailaddress.com" `
    -subject "SQL Test Report"  

Create an Email With Multiple Arrays of Data: MODERATE DIFFICULTY



Import-Module \\pathtoscriptfile\Get-DGMEmailReport.psm1 -Force 

#Create Some Arrays Of Data To Display in Report. You can create as many as you want.
$OutputArrays = @()
   
#Array1
$output = [PSCustomObject] @{
'Message' = "These are deployments older than 90 days, with lower than an 80% success rate where 100 or more computers were targeted.";
'Title' = "SCCM Problem Deployments";
'Color' = "Red";
'Array' = Get-DGMSCCMProblemDeploymentsArray -Days 90 -PercentSuccessThreshold .8 -NumberOfTargetedThreshold 100;
}

$OutputArrays+=$output

#Array2
$output = [PSCustomObject] @{
'Message' = "These are all deployments that were deployed over a year ago.";
'Title' = "SCCM Deployments Greater Than 1 Year Old";
'Color' = "Green";
'Array' = Get-DGMSCCMProblemDeploymentsArray -Days 365 -PercentSuccessThreshold 1 -NumberOfTargetedThreshold 0;
}
    
$OutputArrays+=$output
    
#Array3
$output = [PSCustomObject] @{
'Message' = "These are all our SQL Servers.";
'Title' = "SQL Servers";
'Array' = Get-ADComputer -properties Description -filter {description -like "*sql*"} | Select Name,Description
}

$OutputArrays+=$output 

#Multiple Arrays Email Report
Get-DGMEmailReport `
    -Arrays $OutputArrays `
    -ReportTitle "SQL Report 2017" `
    -from "SQLReports@emailaddress.com" `
    -To "c-dmaiolo@emailaddress.com" `
    -subject "SQL Test Report" 

Sample Single Array Output

cid:image002.jpg@01D38FBC.5C3028C0

Sample Multiple Array Output Email

cid:image003.jpg@01D38FBC.5C3028C0

PowerShell Functions


<#
.SYNOPSIS
  Generates an Email Report
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-02
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

Import-Module \\scriptserver\scripts\DMGSCCM\New-DMGCMTraceLog\New-DMGCMTraceLog.psm1 -Force

function New-DMGCombinedHTMLTable{
 param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $DMGCombinedHTMLTable
    )
    $CombinedHTML = ""
    
    foreach ($object in $DMGCombinedHTMLTable){

            $CombinedHTML+= "<div class='summary'>"
            
            if ($object.Title){
                if ($object.Color){
                    $CombinedHTML+= "<style type=`"text/css`" scoped>"
                    $CombinedHTML+= "th {"
                    $CombinedHTML+= "    background-color: "+(Get-DMGColorHexValue($object.Color))+";"
                    $CombinedHTML+= "}"
                    $CombinedHTML+= "</style>"
                    #$CombinedHTML+= "<h2 style=`"color:"+($object.Color)+"`;`">"+($object.Title)+"</h2>"
                    $CombinedHTML+= "<h2 style=`"color:Black`;`">"+($object.Title)+"</h2>"

                }else{
                    $CombinedHTML+= "<h2>"+($object.Title)+"</h2>"
                }
                
            }
            if($object.Message){
                $CombinedHTML+= "<h3 class='fileHeader'>&#x27A5;&nbsp;"+($object.Message)+"</h3>"
            }
            if ($object.Array){
                $CombinedHTML+= $object.Array | ConvertTo-Html
            }
            
            $CombinedHTML+= "</div>"

    }

    return $CombinedHTML
}

function New-DMGHTMLTable{
 param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $Array,
        [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$true)]
        $Title,
        [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$true)]
        $Message,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        $Color
    )
    $HTML = ""
    $HTML+= "<div class='summary'>"

    if([bool]($MyInvocation.BoundParameters.Keys -match 'Title')){
        if([bool]($MyInvocation.BoundParameters.Keys -match 'Color')){
            $HTML+= "<style type=`"text/css`" scoped>"
            $HTML+= "th {"
            $HTML+= "    background-color: "+(Get-DMGColorHexValue($Color))+";"
            $HTML+= "}"
            $HTML+= "</style>"
        }
        $HTML+= "<h2 style=`"color:Black`;`">"+($Title)+"</h2>"
    }
    if([bool]($MyInvocation.BoundParameters.Keys -match 'Message')){
        $HTML+= "<h3 class='fileHeader'>&#x27A5;&nbsp;"+($Message)+"</h3>"
    }
    if([bool]($MyInvocation.BoundParameters.Keys -match 'Array')){
        $HTML+= $Array | ConvertTo-Html
    }

    $HTML+= "</div>"

    return $HTML
}

function New-DMGEmailReport{

 param(
        [Parameter(Position=0,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Multiple Arrays')]
        $Arrays,
        [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')]
        $Array,
        [Parameter(Position=2,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')]
        $ArrayMessage,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')]
        $ArrayTitle,
        [Parameter(Position=9,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')]
        [ValidateSet("Red","Yellow","Green","Black")]
        $ArrayTableColor = "Black",
        [Parameter(Position=4,Mandatory=$true,ValueFromPipeline=$true)]
        $From,
        [Parameter(Position=5,Mandatory=$true,ValueFromPipeline=$true)]
        $To,
        [Parameter(Position=6,Mandatory=$true,ValueFromPipeline=$true)]
        $Subject,
        [Parameter(Position=7,Mandatory=$true,ValueFromPipeline=$true)]
        $ReportTitle,
        [Parameter(Position=8,Mandatory=$false,ValueFromPipeline=$true)]
        $SmtpServer = "mail.emailaddress.com",
        [Parameter(Position=9,Mandatory=$false,ValueFromPipeline=$true)]
        [Switch]$AttatchResults,
        [Parameter(Position=10,Mandatory=$false,ValueFromPipeline=$true)]
        [Switch]$AttatchSource
    )

    #Set Logging Varibales
    $invocation = (Get-Variable MyInvocation -Scope 1).Value
    $ScriptDirectory = Split-Path $invocation.MyCommand.Path
    $ScriptName = ($MyInvocation.MyCommand.Name)+".psm1"
    $LogName = ($MyInvocation.MyCommand.Name)+".log"
    $LogFile = Join-Path $ScriptDirectory $LogName
    $ScriptFile = Join-Path $ScriptDirectory $ScriptName
    $ReportDate = Get-Date 
    $WrapperScriptPath = $MyInvocation.PSCommandPath

    #Map a Drive to UNC Path
    New-PSDrive -Name UNCPath -PSProvider FileSystem -Root $ScriptDirectory

    #Log Start of Function
    New-DMGCMTraceLog -message ("Starting Logging for $ScriptName") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

    #Create Attachments Directory If It Doesn't Exist
    if (!(Test-Path "UNCPath:\Attachments\")){
        New-DMGCMTraceLog -message ("UNCPath:\Attachments\ Not Found. Created.") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
        New-Item "UNCPath:\Attachments\" -ItemType Directory
    }


    if([bool]($MyInvocation.BoundParameters.Keys -contains 'Arrays')){
        $HTMLTable = New-DMGCombinedHTMLTable -DMGCombinedHTMLTable $Arrays
        #Log
        New-DMGCMTraceLog -message ("An Array of Arrays Was Passed") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
        if([bool]($MyInvocation.BoundParameters.Keys -contains 'AttatchResults')){
            $attachment = @()
            
            foreach ($object in $Arrays){
                $AttachmentName = (Remove-DMGInvalidFileNameChars($object.Title))+".csv"
                $AttachmentFile = Join-Path UNCPath:\Attachments\ $AttachmentName
                $object.Array | export-csv -path $AttachmentFile -notypeinformation
                $attachment += $AttachmentFile
            }
            New-DMGCMTraceLog -message ("Attatching multiple files: $attachment") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile 
        }

    }elseif([bool]($MyInvocation.BoundParameters.Keys -contains 'Array')){
        $HTMLTable = New-DMGHTMLTable -Array $Array -Message $ArrayMessage -Title $ArrayTitle -Color $ArrayTableColor
        #Log
        New-DMGCMTraceLog -message ("An Single Array Was Passed") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
        if([bool]($MyInvocation.BoundParameters.Keys -contains 'AttatchResults')){
            $attachment = @()
            $AttachmentName = (Remove-DMGInvalidFileNameChars($ArrayTitle))+".csv"
            $AttachmentFile = Join-Path UNCPath:\Attachments\ $AttachmentName
            $Array | export-csv -path $AttachmentFile -notypeinformation
            $attachment += $AttachmentFile
         }
         New-DMGCMTraceLog -message ("Attatching the single file: $Attachment") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile  
    }

#Construct HTML for Email

$HTMLEmailMessage = @"
<html><head><title>CodeNarc Report: Sample Project</title><style type='text/css'>body {
font-family: Arial, sans-serif;
margin: 20px 20px 20px 30px;
}

h1, h2, h3 {
    font-weight: bold;
}

h1 {
    width: 400px;
    text-align: center;

    color: white;
    background-color: #557799;
    padding: 10px;
}

h2 {
    font-size: 150%;
    margin-top: 20px;
    padding-top: 5px;
    border-top: 5px solid lightgray;
}

h3 {
    margin-left: 5px;
    margin-top: 15px;
}

.metadata {
}

.summary {
    margin-bottom: 20px;
}

.tableHeader {
    font-weight: bold;
}

.number {
    text-align: center;
}

.priority1, .priority2, .priority3, .priority4 {
    font-weight: bold;
    text-align: center;
    color: #990000;
}

.Description {
    background-color: #FFAAAA;
}

.priority2 {
    background-color: #FFCCAA;
}

.priority3 {
    background-color: #FFEEAA;
}

table {
    border: 2px solid gray;
    border-collapse: collapse;

    -moz-box-shadow: 3px 3px 4px #AAA;
    -webkit-box-shadow: 3px 3px 4px #AAA;
    box-shadow: 3px 3px 4px #AAA;
}

td, th {
    border: 1px solid #D3D3D3;
    padding: 2px 6px 2px 6px;
    margin: 8px 6px 8px 6px;
}

th {
    text-shadow: 2px 2px 2px white;
}

th {
    border-bottom: 1px solid gray;
    background-color: #DDDDFF;
}

em, .em {
    font-weight: bold;
}</style></head>
<body>
<h1>$Subject</h1>
<div class='metadata'>
<table><tr><td class='em'>Report Title:</td>
<td>$ReportTitle</td></tr>
<tr><td class='em'>Date:</td>
<td>$ReportDate</td></tr>
</tr></table></div>
$HTMLTable
<p>Path to script: $ScriptFile</p>
<p>Path to log file: $LogFile</p>
<p>Path to script that called $ScriptName`: $WrapperScriptPath</p>
</body></html>
"@

if([bool]($MyInvocation.BoundParameters.Keys -contains 'AttatchSource')){
    if ($attachment -eq $null){$attachment = @()}
    
    $AttachmentName = (Remove-DMGInvalidFileNameChars($ReportTitle))+"_html_source.txt"
    $AttachmentFile = Join-Path UNCPath:\ $AttachmentName
    $HTMLEmailMessage | Out-File -FilePath $AttachmentFile
    $attachment += $AttachmentFile
}
       
    #Send The Email
    if ($Attachment){
        Send-MailMessage -From $from -To $To -Subject $subject -SmtpServer $smtpServer -BodyAsHtml -Body $HTMLEmailMessage -Attachments $Attachment
        #Log Sent Email
        New-DMGCMTraceLog -message ("Email Sent`: Subject`:$subject To`:$To From`:$From Attatchments`:$attachment") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
    }else{
        Send-MailMessage -From $from -To $To -Subject $subject -SmtpServer $smtpServer -BodyAsHtml -Body $HTMLEmailMessage
        #Log Sent Email
        New-DMGCMTraceLog -message ("Email Sent`: Subject`:$subject To`:$To From`:$From") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
    }

    #Remove Attachments
    
    if (Test-Path "UNCPath:\Attachments\"){
        Remove-Item "UNCPath:\Attachments\" -Recurse
        New-DMGCMTraceLog -message ("UNCPath:\Attachments\ Found. Removing files within.") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
    }

    #Log End Of Function
    New-DMGCMTraceLog -message ("End Logging for $ScriptName") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
}

function Get-DMGColorHexValue{

param(
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        $Color
    )

    switch ($Color)
    {
        "Black" {"#000000"} 
        "White" {"#FFFFFF"} 
        "Red" {"#FF0000"} 
        "Lime" {"#00FF00"} 
        "Blue" {"#0000FF"} 
        "Yellow" {"#FFFF00"} 
        "Cyan" {"#00FFFF"}
        "Magenta" {"#FF00FF"}
        "Silver" {"#C0C0C0"} 
        "Gray" {"#808080"} 
        "Maroon" {"#800000"} 
        "Olive" {"#808000"} 
        "Green" {"#008000"} 
        "Purple" {"#800080"} 
        "Teal" {"#008080"}
        "Navy" {"#000080"} 

        default {"#DDDDFF"}
    }

}

Function Remove-DMGInvalidFileNameChars {
  param(
    [Parameter(Mandatory=$true,
      Position=0,
      ValueFromPipeline=$true,
      ValueFromPipelineByPropertyName=$true)]
    [String]$String
  )
    $pattern = '[^a-zA-Z]'
    $CleanString = $String -replace $pattern
    return $CleanString
}

PowerShell Code: The Logging Functions

Import this module to enable the logging functions.


function New-DGMCMTraceLog{
  param (
  [Parameter(Mandatory=$true)]
  $ScriptName,
  [Parameter(Mandatory=$true)]
  $LogFile,
  [Parameter(Mandatory=$true)]
  $ScriptFile,
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  $VerboseLogging = "true"
  [bool]$Verbose = [System.Convert]::ToBoolean($VerboseLogging)
  $MaxLogSizeInKB = 10240
  $ScriptStatus = 'Success'

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}><thread={4}>" -f ($type + ":" + $message), ($ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}><thread={4}>" -f ($type + ":" + $message), ($ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($ScriptStatus -ne 'Error')) { $ScriptStatus = $type }
  if ($type -eq 'Error') { $ScriptStatus = $type }

  if ((Get-Item $LogFile -ErrorAction SilentlyContinue).Length/1KB -gt $MaxLogSizeInKB)
  {
    $log = $LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

Leave a Comment