Using ConvertTo-HTML and CSS to create useful web reports from PowerShell

Many times we in Dev/Ops or a sysadmin role will create tools for our own use to get nicely formatted output in the console.  Normally projects like this have three phases. In the beginning, people are doing something very manually, or have a huge task to accomplish. In the first phase, it’s good enough to make it work, and get people using it ASAP. From there, it’s a great phase of quickly iterating adding more features and refining the scope of the tool or project, and everyone is happy. Well, everyone but you. The final phase has you putting the finishing touches on everything and really polishing it up. I love on the field engineering, because many times I’m saving someone from a terrible load of work, or making what was arduous a simple task. Drawing put the true potential of powershell and converting others is one of my greatest professional joys.

Now, in this scenario we have a list of computers that need to be migrated from Domain A to Domain B using the Quest migration tools.  At the start of the project, we mass imported a list from SCCM of User to Computer correlations into SharePoint, so we use that to track computers and users for migration scheduling.  The need here was for a tool that could pull down a list of computers for a particular day, display needed information like the user’s name and also provide a one-stop portal for us to be notified when a computer comes online.

With a nice head-start provided by one of my colleagues, John O’harra, we had a valid pipeline full of objects, which was being used to write out files needed for our import.

Using a few calculated properties, we were set.

Enumerate-SharePoint | Where-Object WorkstationMigrationComplete-ne 'true' | Select-Object `
          (($_.PrimaryWorkstationName).Length-gt 0){$_.PrimaryWorkstationName}ELSE{"NA"}}},`
      @{Name='Ipv4';Expression={Test-Connection-computername $_.PrimaryWorkstationName -count 1 | 
          select -ExpandProperty IPV4Address | select -expand IPAddressToString }},`
      @{Name='Reachable';Expression={Test-Connection -computername $_.PrimaryWorkstationName -count 1 -quiet}},`
      @{Name='2ndComputer';Expression={if (($_.SecondaryWorkstationName).Length -gt 0){$_.SecondaryWorkstationName}ELSE{"NA"}}},`
      @{Name='2ndIpv4';Expression={if (($_.SecondaryWorkstationName).Length -gt 0){Test-Connection -computername $_.SecondaryWorkstationName -count 1 | 
         select -ExpandProperty IPV4Address | select -expand IPAddressToString }ELSE{"NA"}}},`
      @{Name='2ndReachable';Expression={if (($_.SecondaryWorkstationName).Length -gt 0){Test-Connection -computername $_.SecondaryWorkstationName  -count 1 -quiet}ELSE{"NA"}}}  | 
                Format-Table -auto

 Phase 1; Console Only

It works and has a nice utilitarian vibe going on, but it isn’t really something you’d want to put up on the big screen…

It could be so much better! This is a solid phase one tool. It does its job but really no one will be impressed too much. I had to do more!

So I dug into my vast and terrible HTML knowledge and decided to make use of the Tee-Object command to send my pipelined objects out to a Variable instead.  Tee is definitely a useful command.  Sometimes I’ll use it within a function to output my pipeline to a variable that will persist through a particular phase of a function.

Imaging you had a function that performed a few tasks, Get-Information, Process-Information, and then ActOn-Information.  During the Debugging process, you might want to run through the function a few times and then after running, tweak some of your objects to see which properties you have available.  Commiting the pipeline objects from each phase to a $global:GetInfoObject, $global:ProcessInfoObject and $global:ActOnInfoObject is a great way to run your tools and then conduct post-mortem on them.  In an upcoming blog post, I’ll go more in-depth into this example.

Anyway, once I used Tee-Object, I envisioned running a ConvertTo-HTML command and then allowing the Control Techs to access this file from a share on my PC.

Phase 2; Introduce some sophistication

The first attempt was pretty…meh.


This is what I would define as a phase two offering. It shows a lot of potential and many would say it is good enough to put into production. It is definitely good enough to run on a monitor somewhere, but I knew we could do better.  I pulled out a little bit of CSS I love to use, with some alternating table and column rows.  I then added a header image (courtesy of a screen print from SharePoint 🙂 and immediately the whole process was elevated to something much more professional looking.

Phase 3; Polish!


Of course, the links are just for appearances, but with a bit of work, it could all be functional. This is where I would call this a phase three deliverable and begin looking for my next project. It can serve well in the field, looking good and getting the job done, and won’t embarrass me later.

The beauty about this whole thing is that all of the heavy lifting is done by the ConvertTo-HTML commandlet and using its parameters.

$HTMLoutput | ConvertTo-Html -title “Migrations for $MigrationGroup“ `
   -Head $head
   -pre “<img src=’.\header.png’><P>Use this list as a reference for systems which are not currently online.</P>”
   -post “<h3>For details, contact Migration Central<br>Created on $date by Stephen Owen</h3>” |
    Out-File c:\public\Migration_$MigrationGroup.html

The other bit of the lifting is used in the $header variable, as seen here.  Feel free to adapt this to your own purposes and I hope that it helps you get work done quickly using PowerShell.

$head = @"
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Frameset//EN” “”&gt;
<html><head><title>Unmigrated Systems Report</title><meta http-equiv=”refresh” content=”120″ />
<style type=”text/css”>
body {
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;

#report { width: 835px; }

border-collapse: collapse;
border: none;
font: 10pt Verdana, Geneva, Arial, Helvetica, sans-serif;
color: black;
margin-bottom: 10px;

table td{
font-size: 12px;
padding-left: 0px;
padding-right: 20px;
text-align: left;

table th {
font-size: 12px;
font-weight: bold;
padding-left: 0px;
padding-right: 20px;
text-align: left;

h2{ clear: both; font-size: 130%;color:#354B5E; }

clear: both;
font-size: 75%;
margin-left: 20px;
margin-top: 30px;

p{ margin-left: 20px; font-size: 12px; }

table.list{ float: left; }

table.list td:nth-child(1){
font-weight: bold;
border-right: 1px grey solid;
text-align: right;

table.list td:nth-child(2){ padding-left: 7px; }
table tr:nth-child(even) td:nth-child(even){ background: #BBBBBB; }
table tr:nth-child(odd) td:nth-child(odd){ background: #F2F2F2; }
table tr:nth-child(even) td:nth-child(odd){ background: #DDDDDD; }
table tr:nth-child(odd) td:nth-child(even){ background: #E5E5E5; }
div.column { width: 320px; float: left; }
div.first{ padding-right: 20px; border-right: 1px grey solid; }
div.second{ margin-left: 30px; }
table{ margin-left: 20px; }

21 thoughts on “Using ConvertTo-HTML and CSS to create useful web reports from PowerShell

  1. gajendra d ambi August 6, 2015 / 12:19 am

    When I try to adapter your header it says the string is missing the terminator: “@. I am in need of a table header which can float my tables and fit 2 column tables next to each other than 1 below the other. I wanted to try yours but no luck. appreciate your altruistic attitude in sharing this post. very useful..atleast for novice beginners like me.
    powershell version 4.
    currently i am using this but this doesnt help me float tables next to each other 😦
    $a = “”
    $a = $a + “BODY{background-color:#F0FFFF;}”
    $a = $a + “TABLE{border-width: 5px;border-style: solid;border-color: Purple;border-collapse: collapse;}”
    $a = $a + “TH{border-width: 1px;padding: 10px;border-style: solid;border-color: black;background-color:LightSeaGreen}”
    $a = $a + “TD{border-width: 1px;padding: 10px;border-style: solid;border-color: black;background-color:WhiteSmoke}”
    $a = $a + “”


    • FoxDeploy August 6, 2015 / 4:48 am

      would you mind placing your full code on paste bin and sending me the link? I’d be happy to help!


    • FoxDeploy August 6, 2015 / 9:05 am

      So, somewhere outside of the code you’ve sent me, you’re attempting to use a here-string, which is a special type of variable that begins an ends a line with @” and “@

      I imagine somewhere you’ve got the end of a variable and it looks like this

      $var = @”

      The closing “@ needs to be on it’s own line.


  2. Marcos Silvestrini January 6, 2016 / 7:56 pm

    How do I add my output in your table ? Already tried most can not . My code:
    foreach($server in $hosts_maxime){
    $status_service_maxime=gwmi win32_service -ComputerName $server -Filter “Name LIKE ‘%APPSERVER%'”|
    Where-Object { ($_.StartMode -eq “manual” -or $_.StartMode -eq “auto”)-and ($_.State -eq “Stopped”)}|
    format-table -property __SERVER,STATE,NAME -AutoSize


    • FoxDeploy January 7, 2016 / 11:27 am

      You need to remove the Format-Table step, because that cmdlet is used to render text for display in the console. So, remove that step from the pipeline, and then add $HTMLOutput before the ForEach. Then the following should work.

      $HTMLoutput | ConvertTo-Html -title “Migrations for $MigrationGroup“ `
      -Head $head
      -pre “

      Use this list as a reference for systems which are not currently online.

      -post “

      For details, contact Migration Central
      Created on $date by Stephen Owen

      ” |
      Out-File c:\public\Migration_$MigrationGroup.html


  3. Tosin Vaithilingam April 10, 2016 / 9:53 pm

    Dear Stephen

    By Default Powershell output the info in the form of below example

    Name Size
    DB01 1.2 GB
    DB02 2.3 GB
    DB03 4.9 GB

    Which we can convert to HTML

    Now the thing is I have more than 500 Databases that I need to report on and reading them via scrolling is a pain

    I was thinking can we do something like this

    DB01 DB02 DB03
    1,2 GB 2.3GB 4.9 GB

    This was the number of rows would be lesser and the info will be readable easily.

    I really don’t have a clue how to achieve this will be this in HTML formatting or the PowerShell

    Please help


    • FoxDeploy April 11, 2016 / 5:01 am

      I’m about to release a new post which has a technique I think you will like for just this sort of situation. Stay tuned!


      • tosshal April 11, 2016 / 6:05 am

        Wow my luck so shiny .I asked it at the right time. I am awaiting eagaly for this.


        • FoxDeploy April 11, 2016 / 11:10 am

          Hi Tosshal, check out my newest post, I hope this method helps you


  4. Tosin Vaithilingam April 11, 2016 / 9:06 pm

    Hi Stephen Is this the One with “Building Better P{PowerShell Dashboards. I could not see how I can get the format that I was looking for. Thanks


  5. Tosin Vaithilingam April 11, 2016 / 9:16 pm

    Sorry I wrote too fast before I saw the Download Link :).. I am going to try it and see how this works out.. Thanks again. Will keep you posted. Its an awesome dashboard and a great idea to built a tool for an IT admin …


  6. Blaviken_butcher February 17, 2017 / 7:49 am

    not works, bullshit


    • FoxDeploy February 17, 2017 / 8:01 am

      Don’t give up Geralt! What kind of problems did you get?


      • MorganM February 27, 2017 / 11:22 am

        There’s something wrong with the $head code … it’s like missing a double quote or something. If you just copy > paste then everything below it in your script thinks it’s all encapsulated in a double quoted code block. I see your code opens with $head = @” but there’s no closing double quote after

        What I haven’t figured out is why putting a double quote at the end of doesn’t fix it. I’m sure I’ll fix it soon but I was scrolling through the comments hoping someone else saw the problem and posted the fix. That’s when I saw Blaviken’s shitpost.


        • MorganM February 27, 2017 / 11:42 am

          Figured it out; you’re missing the “@ to close off the here-string


        • FoxDeploy March 2, 2017 / 9:37 am

          Ahh, I see what you’re saying.

          Check the spacing, that syntax of @” denotes a here-string, which is a way of defining a large multi lined string which might contain both single and double quotes. PowerShell would normally try evaluating those various quotes, so you use a here-string to get around that.

          The common bug with a here-string is that the first and last lines of a here-string must begin and end like so

          whatever stuff goes here

          The closing “@ can’t have any chars before or after it, it needs to be on it’s own line.


  7. SanjayK April 13, 2017 / 8:37 am

    This report looks awesome. Need one help. is it possible to show this Html report on windows form with two buttons like “refresh” and “quit”. Once i click refresh it will refresh the report data on form. quit will help me to exit.
    if this can be done, please do share the code to achieve this.
    Thanks in advance


    • FoxDeploy April 14, 2017 / 9:08 pm

      Hmm…this is a very interesting idea.ill have to think about how to do that


Have a code issue? Share your code by going to and pasting your code there, then post the link here!

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

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

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.