Learn What IT Pros Need to Know About Windows 11 - August 24th at 1 PM ET! Learn What IT Pros Need to Know About Windows 11 - August 24th at 1 PM ET!

Enhancing HTML Reports with PowerShell

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.



Sponsored Content

Read the Best Personal and Business Tech without Ads

Staying updated on what is happening in the technology sector is important to your career and your personal life but ads can make reading news, distracting. With Thurrott Premium, you can enjoy the best coverage in tech without the annoying ads.

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.

Related Topics:


Don't have a login but want to join the conversation? Sign up for a Petri Account

Comments (0)

Leave a Reply

Register for Advanced Microsoft 365 Day!

GET-IT: Advanced Microsoft 365 1-Day Virtual Conference - Live August 24th!

Join us on Tuesday, August 24th and hear from Microsoft MVPs and industry experts about how to take advantage of Microsoft 365 at a technical level and dive deep into the features and functionality that will make your environment more secure and compliant.


Sponsored By