HTML E-Mail Report Generator

Overview

If you’d like a convenient way to take and data and have it turn into an email report, this module might be for you. If you wanted it to create the email on a schedule, just create it as a scheduled task.

  • Use the new –attatchresults switch to attach each of the tables as a csv to the email
  • Use the new –attatchsource switch to attach the HTML source of the email as an attachment (for debugging, etc)

The color value changes the table header.

So, as an example, something like this:


Get-DGMEmailReport `
    -Arrays $OutputArrays `
    -ReportTitle "Last Seven Days SCCM Deployments Report" `
    -from SCCMDeployments@emailaddress.com" `
    -To "c-dmaiolo@emailaddress.com" `
    -subject "This Week's New SCCM Deployments" `
    -AttatchResults `
    -AttatchSource 

Creates this:

cid:image001.jpg@01D38FBC.5C3028C0

If you’d like a convenient way to take and data and have it turn into an email report, this module might be for you. If you wanted it to create the email on a schedule, just create it as a scheduled task.

Create an Email with A Simple Array of Data: EASY

Import-Module \\vendscr001prd\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 and 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 Code


<#
.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 \\vcomscr001prd\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

Your email address will not be published.