PowerShell in Depth: Part 2

PowerShell in Depth: Part 2

  • Comments 2
  • Likes

Summary: In today’s blog, we continue our excerpt from the upcoming book by Don Jones, Richard Siddaway, and Jeffery Hicks.

Microsoft Scripting Guy, Ed Wilson, is here. Yesterday, we posted PowerShell in Depth: Part 1, an excerpt provided by Candace Gillhoolley from Manning Publications. The book, PowerShell in Depth, is written by Don Jones, Richard Siddaway, and Jeffery Hicks.  Today we have the conclusion to that blog.

Image of book cover

There’s definitely a trick to creating reports with Windows PowerShell. Windows PowerShell isn’t at its best when it’s forced to work with text; objects are where it excels. This blog, based on Chapter 33 from PowerShell in Depth, focuses on a technique that can produce a nicely formatted HTML report, suitable for emailing to a boss or colleague.

Assembling the final HTML page

Assembling the final page simply involves adding our two existing fragments, although we are also going to embed a style sheet. Using cascading style sheet (CSS) language is a bit beyond the scope of this blog, but this example will give you a basic idea of what it can do. This embedded style sheet lets us control the formatting of the HTML page, so that it looks a little nicer. If you would like a good tutorial and reference to CSS, check out this CSS Tutorial on the w3schools.com site.

$head = @'


body { background-color:#dddddd;


       font-size:12pt; }

td, th { border:1px solid black;

         border-collapse:collapse; }

th { color:white;

     background-color:black; }

table, tr, td, th { padding: 2px; margin: 0px }

table { margin-left:50px; }




ConvertTo-HTML -head $head -PostContent $frag1,$frag2 `

-PreContent "<h1>Hardware Inventory for SERVER2</h1>"

We have put that style sheet into the $head variable by using a here string to type out the entire CSS syntax that we wanted. That gets passed to the Head parameter, our HTML fragments to the PostContent parameter, and we couldn’t resist adding a header for the whole page, where we’ve again hardcoded a computer name (SERVER2).

We saved the entire script as C:\Good.ps1, and ran it like this:

./good > Report.htm

That directs the output HTML to Report.htm. This HTML report consists of multiple HTML fragments, which is incredibly beautiful as shown here:

Image of report

Okay, maybe it is no work of art, but it is highly functional, and frankly, it looks better than the on-screen-only report we started with in yesterday’s blog. List 2 shows the completed script, where we’ve swapped out the hard-coded computer name for a script-wide parameter that defaults to the local host. Notice too that we’ve included the [CmdletBinding()] declaration at the top of the script, which enables the Verbose parameter. We have used Write-Verbose to document what each step of the script is doing.

List 2: An HTML inventory report script



Retrieves inventory information and produces HTML


./Good > Report.htm


The name of a computer to query. The default is the local computer.






# function to get computer system info

function Get-CSInfo {


  $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername

  $cs = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername

  $bios = Get-WmiObject -Class Win32_BIOS -ComputerName $computername

  $props = @{'ComputerName'=$computername

             'OS Version'=$os.version

             'OS Build'=$os.buildnumber

             'Service Pack'=$os.sevicepackmajorversion



             'BIOS Serial'=$bios.serialnumber}


  $obj = New-Object -TypeName PSObject -Property $props

  Write-Output $obj



Write-Verbose 'Producing computer system info fragment'

$frag1 = Get-CSInfo -computername $computername |

ConvertTo-Html -As LIST -Fragment -PreContent '<h2>Computer Info</h2>' |



Write-Verbose 'Producing disk info fragment'

$frag2 = Get-WmiObject -Class Win32_LogicalDisk -Filter 'DriveType=3' `

         -ComputerName $computername |

Select-Object @{name='Drive';expression={$_.DeviceID}},

              @{name='Size(GB)';expression={$_.Size / 1GB -as [int]}},

        @{name='FreeSpace(GB)';expression={$_.freespace / 1GB -as [int]}} |

ConvertTo-Html -Fragment -PreContent '<h2>Disk Info</h2>' |



Write-Verbose 'Defining CSS'

$head = @'


body { background-color:#dddddd;


       font-size:12pt; }

td, th { border:1px solid black;

         border-collapse:collapse; }

th { color:white;

     background-color:black; }

table, tr, td, th { padding: 2px; margin: 0px }

table { margin-left:50px; }




Write-Verbose 'Producing final HTML'

Write-Verbose 'Pipe this output to a file to save it'

ConvertTo-HTML -head $head -PostContent $frag1,$frag2 `

-PreContent "<h1>Hardware Inventory for $ComputerName</h1>"


Now that’s a script you can build upon! Using the script is very easy.


PS C:\> $computer = SERVER01

PS C:\> C:\Scripts\good.ps1 -computername $computer |

>> Out-File "$computer.html"


PS C:\> Invoke-Item "$computer.html"

The script runs, produces an output file for future reference, and displays the report. Keep in mind that our work to build the Get-CSInfo function is reusable. Because that function outputs an object, not only pure text, you could repurpose it in a variety of places where you might need the same information.

To add to this report, you would:

  1. Write a command or function that generates a single kind of object, which contains all the information you need for a new report section.
  2. Use that object to produce an HTML fragment, and store it in a variable.
  3. Add that new variable to the list of variables in the script’s last command, thus adding the new HTML fragment to the final report.
  4. Sit back and relax.

Yes, this report is text. Ultimately, every report will be because text is what humans read. The point of this one is that everything stays as Windows PowerShell-friendly objects until the last possible instance. We let Windows PowerShell, rather than our own fingers, format everything for us. The actual working bits of this script, which retrieve the information we need, could easily be copied and pasted and used elsewhere for other purposes. That was not as easy to do with our original pure-text report because the actual working code was embedded with all of that formatted text.

Building reports is certainly a common need for administrators, and Windows PowerShell is well suited to the task. The trick, we feel, is to produce reports in a way that makes the reports’ functional code (the bits that retrieve information and so forth) somewhat distinct from the formatting- and the output-creation code. In fact, Windows PowerShell is generally capable of delivering great formatting with very little work on your part, as long as you work it the way it needs you to.

~Don, Richard, and Jeffery

Thank you Candace, Don, Richard, and Jeffery.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • You guys have just sold a copy of your book. What an excellent article it just helped me out so much. I am in the middle of writing a reporting script and was wondering how to do all the html for it.

  • Excellent guys. I can’t wait to read the rest of your new book.

    Here are a couple of useful techniques for assembling HTML reports using this method.  I am sure there are many more.

    Use -Pre and -Post to Add '<div id="div1">' and '</div>' wrappers to each fragment.   These can then be positioned very nicely using CSS.  "<style='float: left....” or "<style='position: absolute ...".

    I have also externalized the style sheets an used the -CssUri to point to it.  This allows us to insert a very large style sheet that can be used as a foundation style leaving only positioning bits to insert. This is very useful for generating very nice email bodies.