PowerShell Pipeline

HTML Reporting in PowerShell, Part 2: Take Your Reporting a Step Further

Customize your reports and get them ready for e-mail through PowerShell with these tips.

In the next part of our small series on creating HTML reports using PowerShell (here's part 1), we will extend what we are doing with our reporting by adding some extra bells and whistles to our report.

You may remember this report from my previous article showing some services on my computer and how we created a title and footer to give it a better look:

[Click on image for larger view.]  Figure 1. Our report from the last article.

Well, now we are going to add some custom Cascading Style Sheet (CSS) code to enhance our report as well making our tables look a little nicer as well. With CSS, the options are almost completely limited by our imagination of how we want the report to look.

$head=@"
<style>
h1 {
text-align:center;
border-bottom:1px solid #666666;
color:blue;
}
TABLE {
TABLE-LAYOUT: fixed;
FONT-SIZE: 100%;
WIDTH: 100%;
BORDER: 1px solid black;
border-collapse: collapse;
}
* {
margin:0
}

              .pageholder {
margin: 0px auto;
}

td {
VERTICAL-ALIGN: TOP;
FONT-FAMILY: Tahoma;
WORD-WRAP: break-word;
BORDER: 1px solid black;         
}

th {
VERTICAL-ALIGN: TOP;
COLOR: #018AC0;
TEXT-ALIGN: left;
background-color:LightSteelBlue;
color:Black;
BORDER: 1px solid black;
}
body {
text-align:left;
font-smoothing:always;
width:100%;
}       
tr:nth-child(even) {
background-color: #dddddd
}  
tr:nth-child(odd) {
background-color: #ffffff
}         
</style>
"@
$pre = @"
<H1>$Env:Computername Services Report </H1>  
"@
$body = $Services | ConvertTo-Html -Fragment | Out-String
$HTML = $pre, $body
$post = "<BR><i>Report generated on $((Get-Date).ToString()) from $($Env:Computername)</i>"
ConvertTo-HTML -Head $head -PostContent $post -Body $HTML | Out-String | Out-File $Report
Invoke-Item $Report

[Click on image for larger view.]  Figure 2. Adding some custom highlighting on the table.

You can see my use of the –Head parameter that allows the use of inline code for CSS. If I wanted, I could also use the –CSSUri parameter to point to a file which contains this code. Looking at the CSS code, you can see that I specify how the title looks, such as the font color used as well as setting up the design of my table that I have. The property header is a light blue color and also making the rows have alternating color to help with the readability of the data. Also by doing this, the data itself is better formatted so I can easily read the RequiredServices, which before were pretty close to the Status data to the point that it might be hard to read.

An important side note: if you plan on embedding your HTML report in an e-mail, then the approach that I showed you with setting alternative row colors will not work. Instead, you will have to perform this action another way by modifying the class in the table's row to make a reference to a CSS value that we will add. Our new CSS code looks like the following:

$head=@"
<style>
h1 {
text-align:center;
border-bottom:1px solid #666666;
color:blue;
}
TABLE {
TABLE-LAYOUT: fixed;
FONT-SIZE: 100%;
WIDTH: 100%;
BORDER: 1px solid black;
border-collapse: collapse;
}
* {
margin:0
}

              .pageholder {
margin: 0px auto;
}

td {
VERTICAL-ALIGN: TOP;
FONT-FAMILY: Tahoma;
WORD-WRAP: break-word;
BORDER: 1px solid black;         
}

th {
VERTICAL-ALIGN: TOP;
COLOR: #018AC0;
TEXT-ALIGN: left;
background-color:LightSteelBlue;
color:Black;
BORDER: 1px solid black;
}
body {
text-align:left;
font-smoothing:always;
width:100%;
}       
.even { background-color: #dddddd; }
.odd { background-color: #ffffff; }         
</style>
"@

Most of our code will look the same, except now we need to take the HTML that we have created.

#region Linq parsing 
$xml = [System.Xml.Linq.XDocument]::Parse( $body)
if($Namespace = $xml.Root.Attribute("xmlns").Value) {
$Namespace = "{{{0}}}" -f $Namespace
}
# Find the index of the column you want to format:
$Index = [Array]::IndexOf( $xml.Descendants("${Namespace}th").Value, "Status")
$i=0
foreach($row in $xml.Descendants("${Namespace}tr")){
If ($i % 2){
Write-Verbose 'Set even' -Verbose
$row.SetAttributeValue("class", "even")
} Else{
Write-Verbose 'Set odd' -Verbose
$row.SetAttributeValue("class", "odd")
}
$i++
}
$Body = $xml.Document.ToString()
#endregion Linq parsing

$HTML = $pre, $body
$post = "<BR><i>Report generated on $((Get-Date).ToString()) from $($Env:Computername)</i>"
ConvertTo-HTML -Head $head -PostContent $post -Body $HTML | Out-String | Out-File $Report
Invoke-Item $Report

[Click on image for larger view.]  Figure 3. Prepping our report as an embedded e-mail.

Now we have a report that can be e-mailed out and not lose its alternating rows. To wrap this up, I will show one last thing that can add some extra coolness to your report by highlighting certain cells or rows based on a comparison done against a cell.

I can use the same code as before but instead I will add a little more to the code that looks at a specific cell in a column and if it returns $True in the comparison, then it will flag red for a service that is stopped.

#region Data gathering 
$Services = Get-Service | Select Name, DisplayName, Status, @{L='RequiredServices';E={$_.RequiredServices -join '; '}}
$Report = 'ServicesReport.html'
#endregion Data gathering

#region Highlighting systems with stopped services
Add-Type -AssemblyName System.Xml.Linq
$head=@"
<style>
h1 {
text-align:center;
border-bottom:1px solid #666666;
color:blue;
}
TABLE {
TABLE-LAYOUT: fixed;
FONT-SIZE: 100%;
WIDTH: 100%;
BORDER: 1px solid black;
border-collapse: collapse;
}
* {
margin:0
}

              .pageholder {
margin: 0px auto;
}

td {
VERTICAL-ALIGN: TOP;
FONT-FAMILY: Tahoma;
WORD-WRAP: break-word;
BORDER: 1px solid black;         
}

th {
VERTICAL-ALIGN: TOP;
COLOR: #018AC0;
TEXT-ALIGN: left;
background-color:LightSteelBlue;
color:Black;
BORDER: 1px solid black;
}
body {
text-align:left;
font-smoothing:always;
width:100%;
}     
.even { background-color: #dddddd; }
.odd { background-color: #ffffff; }                            
</style>
"@
$pre = @"
<H1>$Env:Computername Services Report </H1>  
"@
$body = $Services | ConvertTo-Html -Fragment | Out-String

#region Linq parsing
$xml = [System.Xml.Linq.XDocument]::Parse( $body)
if($Namespace = $xml.Root.Attribute("xmlns").Value) {
$Namespace = "{{{0}}}" -f $Namespace
}
# Find the index of the column you want to format:
$Index = [Array]::IndexOf( $xml.Descendants("${Namespace}th").Value, "Status")
$i=0
foreach($row in $xml.Descendants("${Namespace}tr")){
If ($i % 2){
Write-Verbose 'Set even' -Verbose
$row.SetAttributeValue("class", "even")
} Else{
Write-Verbose 'Set odd' -Verbose
$row.SetAttributeValue("class", "odd")
}
switch(@($row.Descendants("${Namespace}td"))[$Index]) {
{'Stopped' -eq $_.Value } {
Write-Verbose 'Set red' -Verbose
$_.SetAttributeValue( "style", "background: red;")
continue
}
}
$i++
}
$Body = $xml.Document.ToString()
#endregion Linq parsing

$HTML = $pre, $body
$post = "<BR><i>Report generated on $((Get-Date).ToString()) from $($Env:Computername)</i>"
ConvertTo-HTML -Head $head -PostContent $post -Body $HTML | Out-String | Out-File $Report
Invoke-Item $Report
#endregion Highlighting systems with stopped services

[Click on image for larger view.]  Figure 4. Our report showing alternating row colors and highlighting stopped services easily.

Hopefully you liked what you saw with the code presented and will find a way to utilize some, if not all of it in your day-to-day activities!

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular