Recently I went through a series of articles building a hot fix reporting tool. If I recall correctly, part of the original forum post was about creating reports and I think that meant HTML reports. Well, even if it didn’t, IT pros (or maybe their managers) love HTML reports. PowerShell makes this a relatively simple task with ConvertTo-HTML. With this cmdlet and the hot fix function, it is pretty easy to create an html report.
Get-MyHotFix -Computername chi-p50,chi-hvr2 -After 5/1/2016 | ConvertTo-html -Title "HotFix Report" | out-file C:workreport.htm
Don’t forget you still need to save the results to a file.

The report is pretty bare bones. But if you look at help for ConvertTo-HTML, you’ll see that you can specify a path to a CSS file.
Get-MyHotFix -Computername chi-p50,chi-hvr2 -After 5/1/2016 | ConvertTo-html -Title "HotFix Report" -CssUri C:scriptsblue.css | out-file C:workreport.htm

If you are saving the file to a network share or to an intranet server, as along as the CSS file is available to everyone this works pretty well. But I wouldn’t want to have to type that long command everytime I needed a report. Or, suppose this was a monthly task and I was out of the office. I don’t want to have to rely on someone else to type the command correctly. This is where a PowerShell script is useful. All I need to do is copy and paste the command into a .ps1 file and run it. But since I’m going to go the effort of creating a script file, I might as well create something meaningful.
Let’s say that every month I need to run a report that shows all hot fixes installed in the last 45 days on all my servers. The 45 day mark is arbitrary and I want to allow the option to search for a different number of days to satisfy the boss’ whims. I also know the file name will always be the same, but there may be times I need a different path. I can create a script with parameters to meet these requirements.
Param( [int]$Days = 45, $Path = "C:workHotFixReport.htm" )
The script will always process the same computers pulled from a CSV file, so that will be the first line of my script.
$computers = import-csv C:scriptscomputers.csv
I probably should have some error handling, and this could also have been a parameter, but for the sake of demonstration will go with this.
</H2>"
$fragments+= $item.Group | Select-Object -Property * -ExcludeProperty Computername |
ConvertTo-HTML -Fragment
}
The $fragments variable is now the collection of HTML code. Personally, I like to embed the CSS style into the document to make it portable, so my script is going to define a head section as well as a footer with the report date.
#html report title
$ReportTitle = "Company Hotfix Report - $Days Days"
#define a header with an embedded style sheet
$head = @"
body { background-color:#d5dbdb;
font-family:tahoma;
font-size:10pt; }
td, th { border:1px solid black;
border-collapse:collapse; }
th { color:white;
background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
table { width:95%;margin-left:5px; margin-bottom:20px;}
</style>
<br>
<H1>$ReportTitle</H1>
"@
$footer = "<H5><i>Report run $(Get-Date)</i></H5>"
All that remains is to create the HTML report.
ConvertTo-HTML -Head $head -body $fragments -PostContent $footer | Out-File -FilePath $Path -Encoding ascii
Here’s the final result:

So I show the finished report to the boss who gives me a new requirement that Security Updates should be highlighted. I also realize that the online link is a url. So wouldn’t it be nice to be able to click on it to learn more about a given hotfix? These changes will require me to modify the HTML code on the fly. There’s nothing in the Convertto-HTML cmdlet that will help.
To highlight the security update, I can change the font color to red is the description matches ‘Security Update’. I can do this by inserting a class with a corresponding style entry.
.security { color:red;}
Modifying the HTML to detect and insert is a bit trickier. The trick I use is to save the fragment as XML.
[xml]$frag = $item.Group | Select-Object -Property * -ExcludeProperty Computername |ConvertTo-HTML -Fragment -as Table
With an XML document, it is easier to find things using an XPath query and then modify the matching nodes. In my case, that means inserting the class attribute.
$frag.SelectNodes("//td[text()='Security Update']") | foreach {
$class = $frag.CreateAttribute("class")
$class.value = 'security'
$_.Attributes.append($class) | Out-Null
}
The XPath query is looking for td elements with a text value of ‘Security Update’. I can do something similar with the online url.
$frag.SelectNodes("//*[contains(text(),'http')]") | foreach {
#get the current value
$url = $_.'#text'
#replace the value with html link
$_.'#text' = "<a href=$url target=_blank>$url</a>"
}
In this case I am replacing the text with an html link. The last step for this trick to work is to replace some characters in the InnerXML property.
$fragments+= $frag.InnerXml.replace("<","<").Replace(">",">" )
The $Fragments variable is now HTML code and the rest of the script is the same, but with better results.

That seems pretty snazzy to me! Here’s the complete script:
#requires -version 4.0
#create an HTML hotfix report
Param(
[int]$Days = 45,
$Path = "C:workHotFixReport.htm"
)
#import computer information
$computers = Import-Csv C:scriptscomputers.csv
#dot source the hot fix function
. C:scriptsAdvancedFunction-HotfixReport.ps1
#get all hotfixes installed since $Days days ago
Write-Host "Getting hot fix data...please wait" -foregroundcolor magenta
#group data by computername
$data = $computers | Get-MyHotFix -After (Get-Date).AddDays(-$days) | Group-Object -Property Computername
Write-Host "Preparing report..." -foregroundcolor magenta
#initialize an empty array
$fragments=@()
#create a fragments for each computername
foreach ($item in $data) {
#define a heading with the computer name and total number of hotfixes
$fragments+="<H2>$($item.name) [$($item.count)]</H2>"
#convert data to an XML fragment
[xml]$frag = $item.Group | Select-Object -Property * -ExcludeProperty Computername |
ConvertTo-HTML -Fragment -as Table
#insert security class for Security Updates
$frag.SelectNodes("//td[text()='Security Update']") | foreach {
$class = $frag.CreateAttribute("class")
$class.value = 'security'
$_.Attributes.append($class) | Out-Null
}
#turn urls into links. This assumes the entire text value is a url
$frag.SelectNodes("//*[contains(text(),'http')]") | foreach {
#get the current value
$url = $_.'#text'
#replace the value with html link
$_.'#text' = "<a href=$url target=_blank>$url</a>"
}
#replace XML characters for <> in the body
$fragments+= $frag.InnerXml.replace("<","<").Replace(">",">" )
}
#html report title
$ReportTitle = "Company Hotfix Report - $Days Days"
#define a header with an embedded style sheet
$head = @"
<Title>$ReportTitle</Title>
<style>
body { background-color:#D5DBDB;
font-family:Tahoma;
font-size:10pt; }
td, th { border:1px solid black;
border-collapse:collapse; }
th { color:white;
background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
table { width:95%;margin-left:5px; margin-bottom:20px;}
.security { color:red;}
</style>
<br>
<H1>$ReportTitle</H1>
"@
$footer = "<H5><i>Report run $(Get-Date)</i></H5>"
#create the HTML report and save to a file
ConvertTo-HTML -Head $head -body $fragments -PostContent $footer | Out-File -FilePath $Path -Encoding ascii
#display the file object
Get-item -Path $path
But you know, one thing that I find annoying with this report, and hot fixes in general, is that I don’t know what problem the hot fix solving. If I click an online link I can read an article. What would be really handy would be to display the article title in the report. Wouldn’t that be useful? But it is tricky and not something I want to start now so check back later and we’ll wrap this up with an advanced HTML hotfix report.