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

What is “Inside Microsoft Teams”?

“Inside Microsoft Teams” is a webcast series, now in Season 4 for IT pros hosted by Microsoft Product Manager, Stephen Rose. Stephen & his guests comprised of customers, partners, and real-world experts share best practices of planning, deploying, adopting, managing, and securing Teams. You can watch any episode at your convenience, find resources, blogs, reviews of accessories certified for Teams, bonus clips, and information regarding upcoming live broadcasts. Our next episode, “Polaris Inc., and Microsoft Teams- Reinventing how we work and play” will be airing on Oct. 28th from 10-11am PST.

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

External Sharing and Guest User Access in Microsoft 365 and Teams

This eBook will dive into policy considerations you need to make when creating and managing guest user access to your Teams network, as well as the different layers of guest access and the common challenges that accompany a more complicated Microsoft 365 infrastructure.

You will learn:

  • Who should be allowed to be invited as a guest?
  • What type of guests should be able to access files in SharePoint and OneDrive?
  • How should guests be offboarded?
  • How should you determine who has access to sensitive information in your environment?

Sponsored by:

Live Webinar: Active Directory Security: What Needs Immediate Priority!Live on Tuesday, October 12th at 1 PM ET

Attacks on Active Directory are at an all-time high. Companies that are not taking heed are being punished, both monetarily and with loss of production.

In this webinar, you will learn:

  • How to prioritize vulnerability management
  • What attackers are leveraging to breach organizations
  • Where Active Directory security needs immediate attention
  • Overall strategy to secure your environment and keep it secured

Sponsored by: