ConvertTo-AdvHTML – New Advanced Function for HTML Reporting
I love using ConvertTo-HTML. There, I’ve said it. The vast number of scripts I write are doing some kind of reporting function and the ability to create simple, fast, yet good looking reports with HTML has been a huge plus for PowerShell. That said though, there’s definitely room for improvement. ConvertTo-AdvHTML is an advanced function that addresses some of the current limitations with current cmdlet. But this blog post isn’t going to a long explanation of how to use the new cmdlet, instead I wanted to write a script that actually used it.
Beginnings
The script had it’s beginnings with the Set-AlternatingRows function I wrote some time back, but that function was very limited. Later came a new version of the Employee Directory script and this is where the ideas behind this script really began to germinate. The problem with that script was I couldn’t–well, not easily–create custom HTML line by line because I had no idea how many columns would exist in the table. And that’s the thing with ConvertTo-HTML, because it has no idea what information you’re going to be throwing at it, so how are you going to do anything with it?
One way, of course, is to use CSS and there is a great method for inserting your own CSS into the resulting report goes a long way towards making some nice looking reports, but how do you get down to the cell level? How do you change the color of a particular row? How to you put in a simple link? Or a image? So I set out adding those capabilities, and even added a cool bar graph that I had developed for the Simple Server Status script.
The Script
As I said, instead of going into a long, dry explanation of how the script works let’s instead write a simple disk information script that uses the function. First things first, get the advanced function from here. Then read the help file here.
For the script we want to be able to give the script a simple list of servers, either directly in the ComputerName parameter or from pipeline. We could expand this to accept information from Get-ADComputer but for a simple script that seemed a little too much! If the free disk space falls below a certain threshold we want the color of the row to be changed, red for being under the threshold we designated ($AlarmPercent in the Param section below) and yellow for anything that falls within 20 percent of that number–so if we say 90 is where we want red to start, then yellow would occur from 70-89. Here’s the Param section:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Param ( | |
[Parameter(ValueFromPipeline=$true)] | |
[String[]]$ComputerName = @("dc","dc2"), | |
[int]$AlarmPercent = "90" | |
) |
DC and DC2 are test servers on my network and for testing I just put them in there to make the testing phase go easier. Then, in my Begin block I put the ConvertTo-AdvHTML function, and I initialize an array variable to hold my data in. $AlarmPercent is when we want the script to react and change the color of the affected row.
The Process block is where all of the magic occurs. First I want to connect to my servers and use Get-CIMInstance to get the data. Get-CIMInstance is the replacement for Get-WMIObject and uses the WSMAN protocol instead of DCOM to get to the WMI database. The problem here is that this really only works on Windows 2012 servers and higher, anything older still wants to use DCOM. This portion of the script handles that by using New-CIMSession and trying both protocols to connect to the server. Assuming that works it will then use the Get-CIMInstance using the CIMSession we created. If for some reason that fails the script pretty much throws up it’s hands and says “whatever” and moves on.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Try { | |
$CimSession = New-CimSession –ComputerName $Computer –ErrorAction Stop | |
} | |
Catch { | |
Clear-Variable EV –ErrorAction SilentlyContinue | |
$CimSession = New-CimSession –ComputerName $Computer –SessionOption (New-CimSessionOption –Protocol Dcom) –ErrorAction SilentlyContinue –ErrorVariable EV | |
} | |
If ($EV) | |
{ Write-Warning "$($Computer): Unable to connect because ""$($Error[0])""" | |
Continue | |
} | |
Try { | |
$Disks = Get-CimInstance Win32_LogicalDisk –Filter "DriveType = 3" –CimSession $CimSession –ErrorAction Stop | |
} | |
Catch { | |
Write-Warning "$($Computer): Unable to retrieve disk information because ""$($Error[0])""" | |
Continue | |
} |
OK, cool, we’ve loaded $Disks with our data and now we need to loop through it (there’s an object for every drive) and create our PSCustomObject with the proper data in it. This is where we start putting in our tags to tell ConvertTo-AdvHTML what we want to do. You did read the help section, right?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ForEach ($Disk in $Disks) | |
{ Clear-Variable Tag –ErrorAction SilentlyContinue | |
$UsedPercent = ($Disk.FreeSpace / $Disk.Size) * 100 | |
If ($UsedPercent -ge $AlarmPercent) | |
{ $Tag = "[row:red]" | |
} | |
ElseIf ($UsedPercent -ge $AlarmPercent – 20) | |
{ $Tag = "[row:yellow]" | |
} |
The idea here is we want to determine the percentage of used space, then check if the disk has moved above that threshold. $Tag is going to be the text tag that tells ConvertTo-AdvHTML what to do with the row and by clearing it if we decide to do nothing then including that variable in our object property won’t do anything at all (there’ll be nothing there). But if it does fall within our thresholds we can set $Tag to be what we want. If it falls over $AlertPercent then we make the tag [row:red] which will cause the entire row to be turned the color red. Fall between the 70-89 range and we’ll set the tag to [row:yellow]. Now let’s create our object:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$Data += [PSCustomObject] @{ | |
Computer = $Computer | |
Drive = $Disk.DeviceID | |
VolumeName = $Disk.VolumeName | |
CapacityGB = "$Tag{0:N2}" -f ($Disk.Size / 1gb) | |
Usage = "[bar:{0:N0};darkgreen;green]{1:N2}GB Free" -f ($UsedPercent),($Disk.FreeSpace / 1gb) | |
} |
I use another tag in the Usage column called “bar”. This essentially creates 2 HTML spans and set’s their color. There’s also a free text portion so I decided to put the available space (in GB) there. The bar is driven by percentages, so the first number is how far the bar should go. So in this case that would be the percentage of space used: ($Disk.Size – $Disk.FreeSpace) / $Disk.Size) * 100. The bar will calculate the rest for you so no need to specify that. After that there are 2 color designations where you can set the used percentage color of the bar, and the remainder bar. For example, if you wanted to you could set the remainder to be fire engine red if the $AlarmPercent threshold was hit. Since I’m changing the whole column no need for that.
Now we’ve created our data, time to move into the End scriptblock and create our HTML:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$HTML = $Data | Sort Computer,Drive | ConvertTo-AdvHTML –Title "Disk Usage Report" ' | |
-PreContent "<h2>Disk Usage Report</h2>" ' | |
–PostContent "<p>Run on: $(Get-Date)</p>" ' | |
-HeadWidth 0,0,0,0,600 |
I don’t normally use the back tick but in this case it does make the usage a little easier to read. Take the data, use Sort by server name and drive letter, then pipe it into ConvertTo-AdvHTML. I don’t supply any CSS but ConvertTo-AdvHTML does supply a simple CSS for you. I should probably take that out the more I think about it. What do you think?
Anyway, I use the PreContent and the PostContent parameters to put a header and footer into the report and Title to name the report in your browser–ever noticed how this doesn’t work with ConvertTo-HTML? FTFY. The use of HeadWidth is critical here. It is what allows me to set the width of a particular column and the column that contains our bar graph absolutely requires this. Otherwise HTML will crush it all down to the smallest amount possible and it will just look awful. Setting a column to 0 will tell HTML to use that default behavior anyway, but using a 600 will set that column to 600px.
So, want to see the final result?
Not bad right, but doesn’t show off much. So just for grins I lowered the $AlarmPercent to 50:
And while the script isn’t meant for production, I expect some people would like to look a the the full code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -Version 3.0 | |
Param ( | |
[Parameter(ValueFromPipeline=$true)] | |
[String[]]$ComputerName = @("dc","dc2"), | |
[int]$AlarmPercent = "50" | |
) | |
Begin { | |
#region Functions | |
Function ConvertTo-AdvHTML | |
{ <# | |
.SYNOPSIS | |
Advanced replacement of ConvertTo-HTML cmdlet | |
.DESCRIPTION | |
This function allows for vastly greater control over cells and rows | |
in a HTML table. It takes ConvertTo-HTML to a whole new level! You | |
can now specify what color a cell or row is (either dirctly or through | |
the use of CSS). You can add links, pictures and pictures AS links. | |
You can also specify a cell to be a bar graph where you control the | |
colors of the graph and text that can be included in the graph. | |
All color functions are through the use of imbedded text tags inside the | |
properties of the object you pass to this function. It is important to note | |
that this function does not do any processing for you, you must make sure all | |
control tags are already present in the object before passing it to the | |
function. | |
Here are the different tags available: | |
Syntax Comment | |
=================================================================================== | |
[cell:<color>]<optional text> Designate the color of the cell. Must be | |
at the beginning of the string. | |
Example: | |
[cell:red]System Down | |
[row:<color>] Designate the color of the row. This control | |
can be anywhere, in any property of the object. | |
Example: | |
[row:orchid] | |
[cellclass:<class>]<optional text> | |
Designate the color, and other properties, of the | |
cell based on a class in your CSS. You must | |
have the class in your CSS (use the -CSS parameter). | |
Must be at the beginning of the string. | |
Example: | |
[cellclass:highlight]10mb | |
[rowclass:<class>] Designate the color, and other properties, of the | |
row based on a class in your CSS. You must | |
have the class in your CSS (use the -CSS parameter). | |
This control can be anywhere, in any property of the | |
object. | |
Example: | |
[rowclass:greyishbold] | |
[image:<height;width;url>]<alternate text> | |
Include an image in your cell. Put size of picture | |
in pixels and url seperated by semi-colons. Format | |
must be height;width;url. You can also include other | |
text in the cell, but the [image] tag must be at the | |
end of the tag (so the alternate text is last). | |
Example: | |
[image:100;200;http://www.sampleurl.com/sampleimage.jpg%5DAlt Text For Image | |
[link:<url>]<link text> Include a link in your cell. Other text is allowed in | |
the string, but the [link] tag must be at the end of the | |
string. | |
Example: | |
blah blah blah [link:www.thesurlyadmin.com]Cool PowerShell Link | |
[linkpic:<height;width;url to pic>]<url for link> | |
This tag uses a picture which you can click on and go to the | |
specified link. You must specify the size of the picture and | |
url where it is located, this information is seperated by semi- | |
colons. Other text is allowed in the string, but the [link] tag | |
must be at the end of the string. | |
Example: | |
[linkpic:100;200;http://www.sampleurl.com/sampleimage.jpg%5Dwww.thesurlyadmin.com | |
[bar:<percent;bar color;remainder color>]<optional text> | |
Bar graph makes a simple colored bar graph within the cell. The | |
length of the bar is controlled using <percent>. You can | |
designate the color of the bar, and the color of the remainder | |
section. Due to the mysteries of HTML, you must designate a | |
width for the column with the [bar] tag using the HeadWidth parameter. | |
So if you had a percentage of 95, say 95% used disk you | |
would want to highlight the remainder for your report: | |
Example: | |
[bar:95;dark green;red]5% free | |
What if you were at 30% of a sales goal with only 2 weeks left in | |
the quarter, you would want to highlight that you have a problem. | |
Example: | |
[bar:30;darkred;red]30% of goal | |
.PARAMETER InputObject | |
The object you want converted to an HTML table | |
.PARAMETER HeadWidth | |
You can specify the width of a cell. Cell widths are in pixels | |
and are passed to the parameter in array format. Each element | |
in the array corresponds to the column in your table, any element | |
that is set to 0 will designate the column with be dynamic. If you had | |
four elements in your InputObject and wanted to make the 4th a fixed | |
width–this is required for using the [bar] tag–of 600 pixels: | |
-HeadWidth 0,0,0,600 | |
.PARAMETER CSS | |
Designate custom CSS for your HTML | |
.PARAMETER Title | |
Specifies a title for the HTML file, that is, the text that appears between the <TITLE> tags. | |
.PARAMETER PreContent | |
Specifies text to add before the opening <TABLE> tag. By default, there is no text in that position. | |
.PARAMETER PostContent | |
Specifies text to add after the closing </TABLE> tag. By default, there is no text in that position. | |
.PARAMETER Body | |
Specifies the text to add after the opening <BODY> tag. By default, there is no text in that position. | |
.PARAMETER Fragment | |
Generates only an HTML table. The HTML, HEAD, TITLE, and BODY tags are omitted. | |
.INPUTS | |
System.Management.Automation.PSObject | |
You can pipe any .NET object to ConvertTo-AdvHtml. | |
.OUTPUTS | |
System.String | |
ConvertTo-AdvHtml returns series of strings that comprise valid HTML. | |
.EXAMPLE | |
$Data = @" | |
Server,Description,Status,Disk | |
[row:orchid]Server1,Hello1,[cellclass:up]Up,"[bar:45;Purple;Orchid]55% Free" | |
Server2,Hello2,[cell:green]Up,"[bar:65;DarkGreen;Green]65% Used" | |
Server3,Goodbye3,[cell:red]Down,"[bar:95;DarkGreen;DarkRed]5% Free" | |
server4,This is quite a cool test,[cell:green]Up,"[image:150;650;https://pughspace.files.wordpress.com/2014/01/test-connection.png%5DTest Images" | |
server5,SurlyAdmin,[cell:red]Down,"[link:http://thesurlyadmin.com%5DThe Surly Admin" | |
server6,MoreSurlyAdmin,[cell:purple]Updating,"[linkpic:150;650;https://pughspace.files.wordpress.com/2014/01/test-connection.png%5Dhttp://thesurlyadmin.com" | |
"@ | |
$Data = $Data | ConvertFrom-Csv | |
$HTML = $Data | ConvertTo-AdvHTML -HeadWidth 0,0,0,600 -PreContent "<p><h1>This might be the best report EVER</h1></p><br>" -PostContent "<br>Done! $(Get-Date)" -Title "Cool Test!" | |
This is some sample code where I try to put every possibile tag and use into a single set | |
of data. $Data is the PSObject 4 columns. Default CSS is used, so the [cellclass:up] tag | |
will not work but I left it there so you can see how to use it. | |
.NOTES | |
Author: Martin Pugh | |
Twitter: @thesurlyadm1n | |
Spiceworks: Martin9700 | |
Blog: http://www.thesurlyadmin.com | |
Changelog: | |
1.0 Initial Release | |
.LINK | |
https://thesurlyadmin.com/convertto-advhtml-help/ | |
.LINK | |
http://community.spiceworks.com/scripts/show/2448-create-advanced-html-tables-in-powershell-convertto-advhtml | |
#> | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory=$true, | |
ValueFromPipeline=$true)] | |
[Object[]]$InputObject, | |
[string[]]$HeadWidth, | |
[string]$CSS = @" | |
<style> | |
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;} | |
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #6495ED;font-size:120%;} | |
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;} | |
</style> | |
"@, | |
[string]$Title, | |
[string]$PreContent, | |
[string]$PostContent, | |
[string]$Body, | |
[switch]$Fragment | |
) | |
Begin { | |
If ($Title) | |
{ $CSS += "`n<title>$Title</title>`n" | |
} | |
$Params = @{ | |
Head = $CSS | |
} | |
If ($PreContent) | |
{ $Params.Add("PreContent",$PreContent) | |
} | |
If ($PostContent) | |
{ $Params.Add("PostContent",$PostContent) | |
} | |
If ($Body) | |
{ $Params.Add("Body",$Body) | |
} | |
If ($Fragment) | |
{ $Params.Add("Fragment",$true) | |
} | |
$Data = @() | |
} | |
Process { | |
ForEach ($Line in $InputObject) | |
{ $Data += $Line | |
} | |
} | |
End { | |
$Html = $Data | ConvertTo-Html @Params | |
$NewHTML = @() | |
ForEach ($Line in $Html) | |
{ If ($Line -like "*<th>*") | |
{ If ($Headwidth) | |
{ $Index = 0 | |
$Reg = $Line | Select-String –AllMatches –Pattern "<th>(.*?)<\/th>" | |
ForEach ($th in $Reg.Matches) | |
{ If ($Index -le ($HeadWidth.Count – 1)) | |
{ If ($HeadWidth[$Index] -and $HeadWidth[$Index] -gt 0) | |
{ $Line = $Line.Replace($th.Value,"<th style=""width:$($HeadWidth[$Index])px"">$($th.Groups[1])</th>") | |
} | |
} | |
$Index ++ | |
} | |
} | |
} | |
Do { | |
Switch –regex ($Line) | |
{ "<td>\[cell:(.*?)\].*?<\/td>" | |
{ $Line = $Line.Replace("<td>[cell:$($Matches[1])]","<td style=""background-color:$($Matches[1])"">") | |
Break | |
} | |
"\[cellclass:(.*?)\]" | |
{ $Line = $Line.Replace("<td>[cellclass:$($Matches[1])]","<td class=""$($Matches[1])"">") | |
Break | |
} | |
"\[row:(.*?)\]" | |
{ $Line = $Line.Replace("<tr>","<tr style=""background-color:$($Matches[1])"">") | |
$Line = $Line.Replace("[row:$($Matches[1])]","") | |
Break | |
} | |
"\[rowclass:(.*?)\]" | |
{ $Line = $Line.Replace("<tr>","<tr class=""$($Matches[1])"">") | |
$Line = $Line.Replace("[rowclass:$($Matches[1])]","") | |
Break | |
} | |
"<td>\[bar:(.*?)\](.*?)<\/td>" | |
{ $Bar = $Matches[1].Split(";") | |
$Width = 100 – [int]$Bar[0] | |
If (-not $Matches[2]) | |
{ $Text = " " | |
} | |
Else | |
{ $Text = $Matches[2] | |
} | |
$Line = $Line.Replace($Matches[0],"<td><div style=""background-color:$($Bar[1]);float:left;width:$($Bar[0])%"">$Text</div><div style=""background-color:$($Bar[2]);float:left;width:$width%""> </div></td>") | |
Break | |
} | |
"\[image:(.*?)\](.*?)<\/td>" | |
{ $Image = $Matches[1].Split(";") | |
$Line = $Line.Replace($Matches[0],"<img src=""$($Image[2])"" alt=""$($Matches[2])"" height=""$($Image[0])"" width=""$($Image[1])""></td>") | |
} | |
"\[link:(.*?)\](.*?)<\/td>" | |
{ $Line = $Line.Replace($Matches[0],"<a href=""$($Matches[1])"">$($Matches[2])</a></td>") | |
} | |
"\[linkpic:(.*?)\](.*?)<\/td>" | |
{ $Images = $Matches[1].Split(";") | |
$Line = $Line.Replace($Matches[0],"<a href=""$($Matches[2])""><img src=""$($Image[2])"" height=""$($Image[0])"" width=""$($Image[1])""></a></td>") | |
} | |
Default | |
{ Break | |
} | |
} | |
} Until ($Line -notmatch "\[.*?\]") | |
$NewHTML += $Line | |
} | |
Return $NewHTML | |
} | |
} | |
#endRegion | |
$Data = @() | |
} | |
Process { | |
ForEach ($Computer in $ComputerName) | |
{ Try { | |
$CimSession = New-CimSession –ComputerName $Computer –ErrorAction Stop | |
} | |
Catch { | |
Clear-Variable EV –ErrorAction SilentlyContinue | |
$CimSession = New-CimSession –ComputerName $Computer –SessionOption (New-CimSessionOption –Protocol Dcom) –ErrorAction SilentlyContinue –ErrorVariable EV | |
} | |
If ($EV) | |
{ Write-Warning "$($Computer): Unable to connect because ""$($Error[0])""" | |
Continue | |
} | |
Try { | |
$Disks = Get-CimInstance Win32_LogicalDisk –Filter "DriveType = 3" –CimSession $CimSession –ErrorAction Stop | |
} | |
Catch { | |
Write-Warning "$($Computer): Unable to retrieve disk information because ""$($Error[0])""" | |
Continue | |
} | |
ForEach ($Disk in $Disks) | |
{ Clear-Variable Tag –ErrorAction SilentlyContinue | |
$UsedPercent = ($Disk.FreeSpace / $Disk.Size) * 100 | |
If ($UsedPercent -ge $AlarmPercent) | |
{ $Tag = "[row:red]" | |
} | |
ElseIf ($UsedPercent -ge $AlarmPercent – 20) | |
{ $Tag = "[row:yellow]" | |
} | |
$Data += [PSCustomObject] @{ | |
Computer = $Computer | |
Drive = $Disk.DeviceID | |
VolumeName = $Disk.VolumeName | |
CapacityGB = "$Tag{0:N2}" -f ($Disk.Size / 1gb) | |
Usage = "[bar:{0:N0};darkgreen;green]{1:N2}GB Free" -f ($UsedPercent),($Disk.FreeSpace / 1gb) | |
} | |
} | |
} | |
} | |
End { | |
$HTML = $Data | Sort Computer,Drive | ConvertTo-AdvHTML –Title "Disk Usage Report" –PreContent "<h2>Disk Usage Report</h2>" –PostContent "<p>Run on: $(Get-Date)</p>" –HeadWidth 0,0,0,0,600 | |
$HTML | Out-File c:\Dropbox\Test\diskusage.html | |
& c:\Dropbox\Test\diskusage.html | |
} |
Update 3/3/2014: So want to change the color of a cell, but don’t want to go through the hassle of ConvertTo-AdvHTML? Here’s another advanced function that might be up your alley: Set-CellColor.
[…] If you liked reading about this, you might want to take a look at my new advanced function ConvertTo-AdvHTML. It allows for far greater control over your HTML […]
[…] a follow-up to the ConvertTo-AdvHTML function, I have also written a much simpler version that simply changes the color of a cell. The […]
[…] you can use your script with any cmdlet, or even other people’s scripts! That’s why ConvertTo-AdvHTML even works! I had a lot of extra code in the script that allowed the output to be just an […]
[…] limited to CSV. Out-Gridview, Format-List, Format-Table, ConvertTo-HTML, ConvertTo-CLIXML, ConvertTo-AdvHTML (that’s mine) are all on the table. When I, and other PowerShell people, talk about the […]
Hi Martin,
great function again !
I just wanted to know how is supposed to be used the paramter -Css ? Could you give an example please ?
I have tried things like : -CSS ” $(Get-content $css)” (where $css is the filepath to my css file)
but it doesnt seem to work.
thanks
Forget it, it actually works fine. Great function
Hi Martin, Nice script !
When I try to use the -Fragment parameter. I get an error. Should is option work the same as when using the ConvertTo-Html command ?
ConvertTo-Html : Parameter set cannot be resolved using the specified named parameters.
At P:\POSHscripts\BWDBA_WebDashboard\BWDBA_Misc.psm1:279 char:39
+ $Html = $Data | ConvertTo-Html <<< Works
————————————————————-
$BackupResultDetail | Select -property Instance,Database,RecoveryModel,BackupType,BackupStatus,StartTime,EndTime,DaysAgo | ConvertTo-HTML -Fragment
Using ConvertTo-AdvHTML –> Gives error
————————————————————-
$BackupResultDetail | Select -property Instance,Database,RecoveryModel,BackupType,BackupStatus,StartTime,EndTime,DaysAgo | ConvertTo-AdvHTML -Fragment
Regards,
Stefan
Hi Stefan, no there’s a known bug with -Fragment that I just haven’t cut the time out to fix!
Thanks for the awesome script!
I found that the Usedpercentage is actually wrong, the script checks for free percentage in reality (free space / capacity).
Anyways, i configured the script slightly differently, and also added a section at the end to send the HTML report embedded in an email:
#requires -Version 3.0
Param (
[Parameter(ValueFromPipeline=$true)]
[String[]]$ComputerName = @(“servername”),
[int]$AlarmPercent = “15”
)
Begin {
#region Functions
Function ConvertTo-AdvHTML
{ <#
.SYNOPSIS
Advanced replacement of ConvertTo-HTML cmdlet
.DESCRIPTION
This function allows for vastly greater control over cells and rows
in a HTML table. It takes ConvertTo-HTML to a whole new level! You
can now specify what color a cell or row is (either dirctly or through
the use of CSS). You can add links, pictures and pictures AS links.
You can also specify a cell to be a bar graph where you control the
colors of the graph and text that can be included in the graph.
All color functions are through the use of imbedded text tags inside the
properties of the object you pass to this function. It is important to note
that this function does not do any processing for you, you must make sure all
control tags are already present in the object before passing it to the
function.
Here are the different tags available:
Syntax Comment
===================================================================================
[cell:] Designate the color of the cell. Must be
at the beginning of the string.
Example:
[cell:red]System Down
[row:] Designate the color of the row. This control
can be anywhere, in any property of the object.
Example:
[row:orchid]
[cellclass:]
Designate the color, and other properties, of the
cell based on a class in your CSS. You must
have the class in your CSS (use the -CSS parameter).
Must be at the beginning of the string.
Example:
[cellclass:highlight]10mb
[rowclass:] Designate the color, and other properties, of the
row based on a class in your CSS. You must
have the class in your CSS (use the -CSS parameter).
This control can be anywhere, in any property of the
object.
Example:
[rowclass:greyishbold]
[image:]
Include an image in your cell. Put size of picture
in pixels and url seperated by semi-colons. Format
must be height;width;url. You can also include other
text in the cell, but the [image] tag must be at the
end of the tag (so the alternate text is last).
Example:
[image:100;200;http://www.sampleurl.com/sampleimage.jpg%5DAlt Text For Image
[link:] Include a link in your cell. Other text is allowed in
the string, but the [link] tag must be at the end of the
string.
Example:
blah blah blah [link:www.thesurlyadmin.com]Cool PowerShell Link
[linkpic:]
This tag uses a picture which you can click on and go to the
specified link. You must specify the size of the picture and
url where it is located, this information is seperated by semi-
colons. Other text is allowed in the string, but the [link] tag
must be at the end of the string.
Example:
[linkpic:100;200;http://www.sampleurl.com/sampleimage.jpg%5Dwww.thesurlyadmin.com
[bar:]
Bar graph makes a simple colored bar graph within the cell. The
length of the bar is controlled using . You can
designate the color of the bar, and the color of the remainder
section. Due to the mysteries of HTML, you must designate a
width for the column with the [bar] tag using the HeadWidth parameter.
So if you had a percentage of 95, say 95% used disk you
would want to highlight the remainder for your report:
Example:
[bar:95;dark green;red]5% free
What if you were at 30% of a sales goal with only 2 weeks left in
the quarter, you would want to highlight that you have a problem.
Example:
[bar:30;darkred;red]30% of goal
.PARAMETER InputObject
The object you want converted to an HTML table
.PARAMETER HeadWidth
You can specify the width of a cell. Cell widths are in pixels
and are passed to the parameter in array format. Each element
in the array corresponds to the column in your table, any element
that is set to 0 will designate the column with be dynamic. If you had
four elements in your InputObject and wanted to make the 4th a fixed
width–this is required for using the [bar] tag–of 600 pixels:
-HeadWidth 0,0,0,600
.PARAMETER CSS
Designate custom CSS for your HTML
.PARAMETER Title
Specifies a title for the HTML file, that is, the text that appears between the tags.
.PARAMETER PreContent
Specifies text to add before the opening tag. By default, there is no text in that position.
.PARAMETER PostContent
Specifies text to add after the closing tag. By default, there is no text in that position.
.PARAMETER Body
Specifies the text to add after the opening tag. By default, there is no text in that position.
.PARAMETER Fragment
Generates only an HTML table. The HTML, HEAD, TITLE, and BODY tags are omitted.
.INPUTS
System.Management.Automation.PSObject
You can pipe any .NET object to ConvertTo-AdvHtml.
.OUTPUTS
System.String
ConvertTo-AdvHtml returns series of strings that comprise valid HTML.
.EXAMPLE
$Data = @”
Server,Description,Status,Disk
[row:orchid]Server1,Hello1,[cellclass:up]Up,”[bar:45;Purple;Orchid]55% Free”
Server2,Hello2,[cell:green]Up,”[bar:65;DarkGreen;Green]65% Used”
Server3,Goodbye3,[cell:red]Down,”[bar:95;DarkGreen;DarkRed]5% Free”
server4,This is quite a cool test,[cell:green]Up,”[image:150;650;https://pughspace.files.wordpress.com/2014/01/test-connection.png%5DTest Images”
server5,SurlyAdmin,[cell:red]Down,”[link:http://thesurlyadmin.com%5DThe Surly Admin”
server6,MoreSurlyAdmin,[cell:purple]Updating,”[linkpic:150;650;https://pughspace.files.wordpress.com/2014/01/test-connection.png%5Dhttp://thesurlyadmin.com”
“@
$Data = $Data | ConvertFrom-Csv
$HTML = $Data | ConvertTo-AdvHTML -HeadWidth 0,0,0,600 -PreContent “This might be the best report EVER” -PostContent “Done! $(Get-Date)” -Title “Cool Test!”
This is some sample code where I try to put every possibile tag and use into a single set
of data. $Data is the PSObject 4 columns. Default CSS is used, so the [cellclass:up] tag
will not work but I left it there so you can see how to use it.
.NOTES
Author: Martin Pugh
Twitter: @thesurlyadm1n
Spiceworks: Martin9700
Blog: http://www.thesurlyadmin.com
Changelog:
1.0 Initial Release
.LINK
https://thesurlyadmin.com/convertto-advhtml-help/
.LINK
http://community.spiceworks.com/scripts/show/2448-create-advanced-html-tables-in-powershell-convertto-advhtml
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,
ValueFromPipeline=$true)]
[Object[]]$InputObject,
[string[]]$HeadWidth,
[string]$CSS = @”
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #6495ED;font-size:120%;}
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
“@,
[string]$Title,
[string]$PreContent,
[string]$PostContent,
[string]$Body,
[switch]$Fragment
)
Begin {
If ($Title)
{ $CSS += “`n$Title`n”
}
$Params = @{
Head = $CSS
}
If ($PreContent)
{ $Params.Add(“PreContent”,$PreContent)
}
If ($PostContent)
{ $Params.Add(“PostContent”,$PostContent)
}
If ($Body)
{ $Params.Add(“Body”,$Body)
}
If ($Fragment)
{ $Params.Add(“Fragment”,$true)
}
$Data = @()
}
Process {
ForEach ($Line in $InputObject)
{ $Data += $Line
}
}
End {
$Html = $Data | ConvertTo-Html @Params
$NewHTML = @()
ForEach ($Line in $Html)
{ If ($Line -like “**”)
{ If ($Headwidth)
{ $Index = 0
$Reg = $Line | Select-String -AllMatches -Pattern “(.*?)”
ForEach ($th in $Reg.Matches)
{ If ($Index -le ($HeadWidth.Count – 1))
{ If ($HeadWidth[$Index] -and $HeadWidth[$Index] -gt 0)
{ $Line = $Line.Replace($th.Value,”$($th.Groups[1])”)
}
}
$Index ++
}
}
}
Do {
Switch -regex ($Line)
{ “\[cell:(.*?)\].*?”
{ $Line = $Line.Replace(“[cell:$($Matches[1])]”,””)
Break
}
“\[cellclass:(.*?)\]”
{ $Line = $Line.Replace(“[cellclass:$($Matches[1])]”,””)
Break
}
“\[row:(.*?)\]”
{ $Line = $Line.Replace(“”,””)
$Line = $Line.Replace(“[row:$($Matches[1])]”,””)
Break
}
“\[rowclass:(.*?)\]”
{ $Line = $Line.Replace(“”,””)
$Line = $Line.Replace(“[rowclass:$($Matches[1])]”,””)
Break
}
“\[bar:(.*?)\](.*?)”
{ $Bar = $Matches[1].Split(“;”)
$Width = 100 – [int]$Bar[0]
If (-not $Matches[2])
{ $Text = ” ”
}
Else
{ $Text = $Matches[2]
}
$Line = $Line.Replace($Matches[0],”$Text “)
Break
}
“\[image:(.*?)\](.*?)”
{ $Image = $Matches[1].Split(“;”)
$Line = $Line.Replace($Matches[0],””)
}
“\[link:(.*?)\](.*?)”
{ $Line = $Line.Replace($Matches[0],”$($Matches[2])“)
}
“\[linkpic:(.*?)\](.*?)”
{ $Images = $Matches[1].Split(“;”)
$Line = $Line.Replace($Matches[0],”“)
}
Default
{ Break
}
}
} Until ($Line -notmatch “\[.*?\]”)
$NewHTML += $Line
}
Return $NewHTML
}
}
#endRegion
$Data = @()
}
Process {
ForEach ($Computer in $ComputerName)
{ Try {
$CimSession = New-CimSession -ComputerName $Computer -ErrorAction Stop
}
Catch {
Clear-Variable EV -ErrorAction SilentlyContinue
$CimSession = New-CimSession -ComputerName $Computer -SessionOption (New-CimSessionOption -Protocol Dcom) -ErrorAction SilentlyContinue -ErrorVariable EV
}
If ($EV)
{ Write-Warning “$($Computer): Unable to connect because “”$($Error[0])”””
Continue
}
Try {
$Disks = Get-CimInstance Win32_LogicalDisk -Filter “DriveType = 3” -CimSession $CimSession -ErrorAction Stop
}
Catch {
Write-Warning “$($Computer): Unable to retrieve disk information because “”$($Error[0])”””
Continue
}
ForEach ($Disk in $Disks)
{ Clear-Variable Tag -ErrorAction SilentlyContinue
$FreePercent = ($Disk.FreeSpace / $Disk.Size) * 100
$FreePercent = “{0:N2}” -f $FreePercent
If ($FreePercent -le ($AlarmPercent + 5))
{ $Tag = “[row:yellow]”
}
If ($FreePercent -le $AlarmPercent)
{ $Tag = “[row:red]”
}
$Data += [PSCustomObject] @{
Computer = $Computer
Drive = $Disk.DeviceID
VolumeName = $Disk.VolumeName
CapacityGB = “$Tag{0:N2}” -f ($Disk.Size / 1gb)
Usage = “[bar:{0:N0};lightgreen;white]{1:N2}GB / $FreePercent % Free” -f ($FreePercent),($Disk.FreeSpace / 1gb)
}
}
}
}
End {
$HTML = $Data | Sort Computer,Drive | ConvertTo-AdvHTML -Title “Exchange Free Space Report” -PreContent “Exchange Free Space Report” -PostContent “Run on: $(Get-Date)” -HeadWidth 0,0,0,0,600
$HTML | Out-File c:\ExchangeHealth\diskusage.html
$htmltail = ”
”
send-mailmessage -to “me@u” -from “u@me.com” -subject “Exchange Free Space Report” -Body (Get-Content c:\Exchangehealth\diskusage.html | out-string) -BodyAsHtml -SMTPserver mailrelayservername
# & c:\Exchangehealth\diskusage.html
}
[…] generate HTML reports on the fly to make someone’s job easier. There is another function ConvertTo-AdvHTML here that requires some HTML knowledge. However this output could be intertwined into ReportHTML […]
Pingback by Generating HTML Reports in PowerShell – Part 4 – Azure Field Notes Blog | August 12, 2016 |
Hi, first thanks for this great script I use it everyday. Today I think I found a little Bug. One of my report I want to Export using your function has a line that look like this :
[RCPSTAT , 2192 , 0000000001900200, lgkinit.c:1017 ]: Tue Sep 20 05:10:03
In your function the loop condition is Until ($Line -notmatch “\[.*?\]”), so your script loop forever because of this line . Just wanted to know why you chose this condition for the loop?
Been a long time since I wrote this script, but I believe I wanted to make sure there were no more formatting tags left in the line. The script is available on Github if you want to contribute! https://github.com/martin9700/ConvertTo-AdvHTML
Ok thank you for your answer. Thats what I thought.
First I love the script. Second, I am a novice at html and css.
My question is how do you separate data in a table so that each value is on a separate line? For example, I have the following data from an array which i used the join operator
$KU = ($TemplateSettings.EnhancedKeyUsage).FriendlyName -join “, ”
This returns the following in the cell
Result1, Result2, Result3
I would like to have the data in the cell displayed like this
Result1
Result2
Result3
Would someone be able to provide an example on how to accomplish this task?
Thanks in advance
Rod