The Surly Admin

Father, husband, IT Pro, cancer survivor

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:


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.


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?


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:


$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:


$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?

diskusage1Not bad right, but doesn’t show off much.  So just for grins I lowered the $AlarmPercent to 50:

diskusage2

And while the script isn’t meant for production, I expect some people would like to look a the the full code:


#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&quot;
"@
$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 = "&nbsp;"
}
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%"">&nbsp;</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.

Advertisement

February 27, 2014 - Posted by | PowerShell - HTML Reporting | , , , ,

14 Comments »

  1. […] 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 […]

    Pingback by How to Create HTML Reports « The Surly Admin | February 27, 2014 | Reply

  2. […] 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 […]

    Pingback by HTML Reporting – A Simple Cell Color Changer « The Surly Admin | March 3, 2014 | Reply

  3. […] 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 […]

    Pingback by New Scripts – Bandwidth Report and Google Charts « The Surly Admin | April 2, 2014 | Reply

  4. […] 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 […]

    Pingback by Exporting User Information « The Surly Admin | July 14, 2014 | Reply

  5. 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

    Comment by Olivier | September 4, 2014 | Reply

    • Forget it, it actually works fine. Great function

      Comment by Olivier | September 4, 2014 | Reply

  6. 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

    Comment by Stefan den Engelsman | September 18, 2014 | Reply

    • Hi Stefan, no there’s a known bug with -Fragment that I just haven’t cut the time out to fix!

      Comment by Martin9700 | September 18, 2014 | Reply

  7. 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
    }

    Comment by murdoc_sa | August 12, 2015 | Reply

  8. […] 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 | Reply

  9. 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?

    Comment by Simon | September 21, 2016 | Reply

    • 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

      Comment by Martin9700 | September 21, 2016 | Reply

      • Ok thank you for your answer. Thats what I thought.

        Comment by simontardif | September 21, 2016

  10. 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

    Comment by Rod | December 15, 2016 | Reply


Leave a Reply to Stefan den Engelsman Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: