PowerShell Problem Solver: Create a Grouped HTML Report with PowerShell

I always talk about “objects in the pipeline” when writing or training about PowerShell. We shouldn’t be thinking about trying to parse text or even be too concerned about individual items. Most of the time we can simply let the PowerShell pipeline do all of the work for us. With that said, you can take control, too. One such situation might be when you want to look at the results of PowerShell expression, where it would help to see results grouped together. Taking this scenario a step further, you may want to create an HTML report using grouped output. If you look at help for Convertto-HTML, you won’t see a –Group parameter or anything that looks like it will help. As I’ll show you, it isn’t too difficult to achieve.

First, let’s look at creating a nice formatted group report in the console. We’ll begin with some data.

​
The variable $data should contain all the files in my Scripts folder. Let's say I want to create a formatted report that shows files grouped by directory.
​
With the Format-Table cmdlet, you can specify a property name to group by. I recommend you sort on that same property first as I've done.
Create a Grouped HTML Report with PowerShell
This isn't too difficult because PowerShell has built-in instructions that tell it how to format a table of file objects. What I want next is to duplicate this in an HTML file. To accomplish this, I'll need an intermediate step to group the data accordingly.
​
Technically, I didn't have to sort before grouping. But the result will be that each group of related files is now sorted first by directory and then length,
Each group of related files is now sorted first by directory and then length. (Image Credit: Jeff Hicks)
Each group of related files is now sorted first by directory and then length. (Image Credit: Jeff Hicks)
I now have a new set of objects. The file objects are in the Group property for each grouped entry. To create the HTML report, I will need to use fragments. Instead of piping directly to Convertto-HTML, I'm going to create my own body. I'll start with a blank string.
​
Now for the fun part. I'm going to enumerate each grouped entry in $grouped. I'm going to use the Name property as my heading with an H2 HTML tag. Next, I'm going to send the collection of files and create an HTML table fragment. A table is the default, so I didn't have specify the –As parameter, but I wanted to demonstrate it in case you needed a list instead.
​
You have to remember that Convertto-HTML will look at any incoming objects and convert all properties, not just the ones you see on the screen. That's why I'm selecting a few objects first. With the body complete, I'm now ready to create the final document.
​
I'm adding style from an external CSS file and including a footer of sorts with –PostContent displaying the current date and time. This is what I end up with:
030315 1314 PowerShellP3
You could follow the same process for any type of object. Because this is likely to be a repeatable process, this is something you want to turn into a PowerShell tool. For those of you panicking thinking you can't do it, relax. Here's my version of a tool that I call Out-HTMLReport.
​
Function Out-HTMLReport {
<#
.SYNOPSIS
Send the output of a command to an HTML report.
.DESCRIPTION
Send output from a PowerShell expression to this command and create a formatted HTML report. For best results you should specify the properties to display or use Select-Object earlier in your expression. You can also group results by a property name.
.PARAMETER InputObject
The output from some PowerShell expression.
.PARAMETER Path
The filename and path for the HTML file.
.PARAMETER PreContent
Content to be used at the beginning of your HTML document. This can include HTML tags.
.PARAMETER CssUri
The path to a CSS file. See examples for how to embed a style sheet in the document.
.PARAMETER As
Data will be formatted as fragments. It can be either a Table (default) or a List. All data will be formatted the same.
.PARAMETER Group
The property to group data on. If you specify a grouping property, this property will not be displayed in the table or list output.
.PARAMETER Properties
The properties to display in the report. The default will be all properties.
.PARAMETER Title
The title for your HTML report.
.PARAMETER Head
Content to use for the Head section of the HTML document.
.PARAMETER PostContent
Content to be used at the end of your HTML document. This can include HTML tags.
.EXAMPLE
PS C:\> get-service | out-htmlreport c:\work\services.htm -properties Name,Displayname,Status
Create a very basic HTML report of service information with no formatting.
.EXAMPLE
PS C:\> get-process | Where Company | Out-HTMLReport -Path c:\work\procs.htm -Properties name,id,ws,vm,pm,path -title "Process Report by Company" -group Company -CssUri C:\scripts\blue.css
Get all processes where a Company property is defined and create an HTML report grouping on the company property. Selected properties for each object will be displayed as a table. This example is using an external CSS file.
.EXAMPLE
PS C:> dir -file | select Fullname,Name,@{Name="Size";Expression={$_.length}},CreationTime,LastWriteTime,@{Name="Age";Expression={(Get-Date) - $_.LastWriteTime}},Attributes,Extension | out-htmlreport c:\work\files.htm -Group extension -as List -title "File Report" -CssUri c:\scripts\blue.css
If you want to use custom properties, define them before sending to Out-HTMLReport. If you will be grouping be sure to include the grouping property.
.EXAMPLE
PS C:\> $data = Get-HotFix
Get hotfix data from the local computer.
PS C:\> $head = @"
<Title>Hot Fix Report</Title>
<style>
body { background-color:#FFFFFF;
       font-family:Tahoma;
       font-size:12pt; }
td, th { border:1px solid black;
         border-collapse:collapse; }
th { color:white;
     background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px;}
</style>
<br>
<H1>Hot Fix Report</H1>
"@
Define a here string for the HTML head. This includes an embedded style sheet.
PS C:\> $paramHash = @{
 Head = $head
 Title = "Hot Fix Report"
 Path = "c:\work\hotfix.htm"
 Group = "Description"
 As = "List"
 PostContent = "<H6>$(Get-Date)</H6>"
 Properties = "HotFixID","InstalledOn","InstalledBy","Caption","PSComputername"
}
Define a hash table of parameter values to splat to Out-HTMLReport
PS C:\> $data | Out-HTMLReport @paramHash
Create the report.
.NOTES
Version     : 1.0
Last Update : February 27, 2015
Author      : Jeff Hicks (@jeffhicks)
Learn more about PowerShell:
Essential PowerShell Learning Resources
**************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .INPUTS [object[]] .OUTPUTS none .LINK ConvertTo-HTML #> [cmdletbinding(SupportsShouldProcess=$True)] Param ( [Parameter(Position=0,Mandatory,HelpMessage = "Enter the name of the report to create")] [ValidateNotNullorEmpty()] [string]$Path, [string]$Group, [ValidateNotNullorEmpty()] [string[]]$Properties="*", [string]$CssUri, [ValidateSet("Table","List")] [ValidateNotNullorEmpty()] [string]$As = "Table", [string]$Title, [string]$Head, [string[]]$PreContent, [string[]]$PostContent, [Parameter(Position=1,Mandatory,ValueFromPipeline, HelpMessage="Enter objects to format")] [ValidateNotNullorEmpty()] [object[]]$InputObject ) Begin { Write-Verbose "Starting $($myinvocation.mycommand)" #copy most of the bound parameters since they will #be passed to Convertto-HTML Write-verbose "PSBoundParameters" write-Verbose ($PSBoundParameters | out-string) #initialize an array to hold all the processed data $data=@() #iniatilize a variable for the HTML body [string[]]$body=@() $body+=$PreContent } #begin Process { #add each input object to $data foreach ($item in $Inputobject) { $data+=$item } } #process End { Write-Verbose "Processing $($data.count) objects" #sort on grouping property if used if ($Group) { Write-Verbose "Grouping on $Group" $data | Group-Object -Property $Group | Sort-Object -Property Name | foreach { $body+="<H2>$($_.Name)</H2>" $body+= $_.Group | Select-Object -property $Properties -ExcludeProperty $Group | ConvertTo-HTML -As $As -Fragment } #foreach } else { Write-Verbose "No grouping" $body+= $data | Select-Object -property $Properties | ConvertTo-HTML -As $As -Fragment } #create the HTML $htmlParams = $PSBoundParameters #remove conflicting or unused parameters "InputObject","Path","Group","Properties", "PreContent","WhatIf","Confirm" | foreach { $htmlparams.Remove($_) | out-null } #add body $htmlParams.Add("Body",$body) Write-Verbose "Using these Convertto-HTML parameters" Write-Verbose ($htmlParams | out-string) #create the HTML $html = ConvertTo-HTML @htmlParams #create the file $html | Out-File -filepath $Path -encoding ASCII Write-Verbose "Report created at $path" Write-Verbose "Ending $($myinvocation.mycommand)" } #end } #end Out-Report function #optional: define an alias Set-Alias -Name ohr -Value Out-HTMLReport

The function is intended to be used at the end of a PowerShell expression is the same way would you use Out-File or Out-Printer. If you look through the function, you’ll see that is a wrapper to Convertto-HTML and uses many of the same parameters. Here’s how you can use it. First, I need some new data.

​
I have 100 event logs from the System log, and I want to create an HTML report grouped on the Source property. I'll use this hashtable of parameters to splat to my function.
​
This makes it easy to create the report.
030315 1314 PowerShellP4
If you don't want to reference an external CSS file you can include style in the head section. First, some new data.
​
Next, define a head section with style.
​

I'll use these parameters and create the report:
030315 1314 PowerShellP5
You can use my function even if you don't want to group output. I hope you've seen what you can accomplish with PowerShell and that it isn't especially complicated, especially if you read full help and examples for commands like Format-Table and ConvertTo-HTML.