Enhancing HTML Reports with PowerShell

PowerShell Text Purple hero
I hope you have been enjoying this little series on tips and tricks for doing more with ConvertTo-HTML and creating great looking reports in PowerShell. Today, I want to continue where we left off. I am assuming you have been following along. If not, take some time to start at the beginning or you may be a bit lost.

Often, I create reports from a number of HTML fragments. Usually, I have multiple pieces of information that I want to display. Perhaps, I want to parse the HTML, so I can do things, such as dynamically add style classes as I did last time. I am going to repeat that process today by reusing the core code from the previous article. In addition to event log information, I want to get some other system information as well. Let’s begin with it empty:

$fragments = @()

The first thing I want to do is embed a graphic file in the document. The easy way would be to use the <IMG> tag and specify the path to a graphic file. However, I like making my HTML files portable or self-contained. The first step is to convert the file to a Base64 string.

$ImagePath = "c:\scripts\db.png"
$ImageBits =  [Convert]::ToBase64String((Get-Content $ImagePath -Encoding Byte))

When I create the IMG tag, I want to include an ALT attribute for the file name. I also need to know the file type.

$ImageFile = Get-Item $ImagePath
$ImageType = $ImageFile.Extension.Substring(1) #strip off the leading .

All that remains is to create the IMG tag.

$ImageTag = "<Img src='data:image/$ImageType;base64,$($ImageBits)' Alt='$($ImageFile.Name)' style='float:left' width='120' height='120' hspace=10>"

The only thing you really need is the src value. You can experiment with style settings or other IMG attributes. Since I want the graphic at the top of the file, I will add it to the array of fragments.

$fragments+= $ImageTag

I know that I am going to include some operating system information, so I might as well add a header for it.

#adjust spacing - takes trial and error
$fragments+= "<br><br>"
$fragments+= "<H2>OS Info</H2>"

I plan on using Get-CimInstance to retrieve operating system information. I only need a few properties converted to an HTML fragment.<p>

​ $fragments+= Get-Ciminstance -ClassName win32_operatingsystem |
Select @{Name="Operating System";Expression= {$_.Caption}},Version,InstallDate |
ConvertTo-Html -Fragment -As List

You should notice that I did something different here. By default, fragments are created as HTML tables. For this section, I wanted a list. You can mix and match lists and tables as you see fit.
Next up is some system information, which will come from this function:

Function Get-SystemInfo {
Param([string]$Computername = $env:COMPUTERNAME)
#this function has no real error handling
$cs = Get-CimInstance -ClassName Win32_computersystem -ComputerName $Computername
#this assumes a single processor
$proc = Get-CimInstance -ClassName win32_processor -ComputerName $Computername
$data = [ordered]@{
TotalPhysicalMemGB = $cs.TotalPhysicalMemory/1GB -as [int]
NumProcessors = $cs.NumberOfProcessors
NumLogicalProcessors = $cs.NumberOfLogicalProcessors
HyperVisorPresent = $cs.HypervisorPresent
DeviceID = $proc.DeviceID
Name = $proc.Name
MaxClock = $proc.MaxClockSpeed
L2size = $proc.L2CacheSize
L3Size = $proc.L3CacheSize
New-Object -TypeName PSObject -Property $data

Remember, you always want to convert objects to HTML. In this situation, I want the system information displayed as a list too.

$fragments+= "<H2>System Info</H2>"
$fragments+= Get-systeminfo -Computername $env:COMPUTERNAME | ConvertTo-Html -Fragment -As List

From here, I will reuse my code from last time to get and process event log information, highlighting empty logs with red text. I will also include my standard footer.

$fragments+= "<H2>EventLog Info</H2>"
[xml]$html  = Get-Eventlog -List |
Select @{Name="Max(K)";Expression = {"{0:n0}" -f $_.MaximumKilobytes }},
@{Name="Retain";Expression = {$_.MinimumRetentionDays }},
OverFlowAction,@{Name="Entries";Expression = {"{0:n0}" -f $_.entries.count}},
@{Name="Log";Expression = {$_.LogDisplayname}} | ConvertTo-Html -Fragment
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
  if ($html.table.tr[$i].td[3] -eq 0) {
    $class = $html.CreateAttribute("class")
    $class.value = 'alert'
    $html.table.tr[$i].attributes.append($class) | out-null
$fragments+= $html.InnerXml
$fragments+= "<p class='footer'>$(get-date)</p>"

The last part is to define my hashtable of parameters for ConvertTo-HTML and create the final document.

$convertParams = @{
  head = @"
 <Title>System Report - $($env:computername)</Title>
body { background-color:#E5E4E2;
       font-size:10pt; }
td, th { border:0px solid black;
         white-space:pre; }
th { color:white;
     background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px ;white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px;}
h2 {
.alert {
 color: red;
{ color:green;
 body = $fragments
convertto-html @convertParams | out-file d:\temp\sysreport.htm

I moved the computer name to the report title. Here is the final result.

A mixed HTML report built with PowerShell (Image Credit: Jeff Hicks)
A Mixed HTML Report Built with PowerShell (Image Credit: Jeff Hicks)

Of course, all of this code is something you would put into a script or function to make it easier to use. Not that the example I have been working with is especially compelling, but I hope you will pay more attention to the techniques I used. That is the real takeaway. If you get stuck with your own PowerShell scripts creating HTML reports, I encourage you to use the PowerShell forums.

Before we leave this topic, I want to come back one more time and show you a technique I think you will find useful.