PowerShell in Depth: Part 1

PowerShell in Depth: Part 1

  • Comments 10
  • Likes

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

Microsoft Scripting Guy, Ed Wilson, is here. Today and tomorrow we have a special treat for you. Candace Gillhoolley, from Manning Publications, sent me an excerpt from the upcoming book, PowerShell in Depth, which is written by Don Jones, Richard Siddaway, and Jeffery Hicks. 

Image of book cover

Here is Part 1...

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 in PowerShell in Depth, focuses on a technique that can produce a nicely formatted HTML report, suitable for emailing to a boss or colleague.

Working with HTML Fragments and Files

Let us begin this blog with an example of what we think is a poor report-generating technique. We see code like this—sadly, more often than we would like. Most of the time, the IT Pro does not know any better and is simply perpetuating techniques from other languages such as VBScript. List 1, which we devoutly hope you will never run yourself, is a very common approach that you will see less-informed administrators take.

List 1: A poorly designed inventory report

param ($computername)

Write-Host '------- COMPUTER INFORMATION -------'

Write-Host "Computer Name: $computername"

 

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

Write-Host "   OS Version: $($os.version)"

Write-Host "     OS Build: $($os.buildnumber)"

Write-Host " Service Pack: $($os.servicepackmajorversion)"

 

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

Write-Host "          RAM: $($cs.totalphysicalmemory)"

Write-Host " Manufacturer: $($cs.manufacturer)"

Write-Host "        Model: $($cd.model)"

Write-Host "   Processors: $($cs.numberofprocessors)"

 

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

Write-Host "BIOS Serial: $($bios.serialnumber)"

 

Write-Host ''

Write-Host '------- DISK INFORMATION -------'

Get-WmiObject -Class Win32_LogicalDisk -Comp $computername -Filt 'drivetype=3' |

Select-Object @{n='Drive';e={$_.DeviceID}},

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

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

Format-Table -AutoSize

This produces a text-based inventory report something like the one shown here:

Image of command output

It does the job, we suppose, but Don has a saying that involves angry deities and puppies, which he utters whenever he sees a script that outputs pure text like this. First of all, this script can only produce output on the screen because it’s using Write-Host. In most cases, if you find yourself using only Write-Host, you are probably doing it wrong. Wouldn’t it be nice to have the option of putting this information into a file or creating an HTML page? Of course, you could achieve that by changing all of the Write-Host commands to Write-Output, but you still wouldn’t be doing things the right way.

There are a lot of better ways that you could produce such a report and that’s what this blog is all about. First, we would suggest building a function for each block of output that you want to produce, and having that function produce a single object that contains all of the information you need. The more you can modularize, the more you can reuse those blocks of code. Doing so would make that data available for other purposes, not only for your report. In our example of a poorly written report, the first section, Computer Information, would be implemented by some function that you would write. The Disk Information section is only sharing information from one source, so it’s actually not that bad off, but all of those Write commands simply have to go.

The trick to our technique lies in the fact that the ConvertTo-HTML cmdlet in Windows PowerShell can be used in two ways, which you’ll see if you examine its Help file. The first way produces a complete HTML page, and the second only produces an HTML fragment. That fragment is a table with whatever data you have fed the cmdlet. We’re going to produce each section of our report as a fragment, and then use the cmdlet to produce a complete HTML page that contains all of those fragments.

Getting the information

We will start by ensuring that we can get whatever data we need formed into an object. We will need one kind of object for each section of our report, so if we’re sticking with Computer Information and Disk Information, that’s two objects.

Note   For brevity and clarity, we are going to omit error handling and other niceties in this example. We would add those in a real-world environment.

Get-WmiObject by itself is capable of producing a single object that has all of the disk information we want, so we simply need to create a function to assemble the computer information. Here it is:

function Get-CSInfo {

  param($computername)

  $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

                         'RAM'=$cs.totalphysicalmemory

                         'Processors'=$cs.numberofprocessors

                         'BIOS Serial'=$bios.serialnumber}

 

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

  Write-Output $obj

}

The function uses the Get-WMIObject cmdlet to retrieve information from three WMI classes on the specified computer. We always want to write objects to the pipeline, so we’re using New-Object to write a custom object to the pipeline by using a hash table of properties culled from the three WMI classes. Normally, we prefer property names to not have any spaces, but, because we’re going to be using this in a larger reporting context, we’ll bend the rules a bit.

Producing an HTML fragment

Now we can use our newly-created Get-CSInfo function to create an HTML fragment.

$frag1 = Get-CSInfo –computername SERVER2 |

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

Out-String

This little trick took us a while to figure out, so it is worth examining.

  • We are saving the final HTML fragment into a variable named $frag1. That will let us capture the HTML content and later insert it into the final file.
  • We are running Get-CSInfo and giving it the computer name we want to inventory. For right now, we are hardcoding the SERVER2 computer name. We will change that to a parameter a bit later.
  • We are asking ConvertTo-HTML to display this information in a vertical list, rather than in a horizontal table, which is what it would do by default. The list will mimic the layout from the old, bad-way-of-doing-things report.
  • We used the PreContent switch to add a heading to this section of the report. We added the <h2> HTML tags so that the heading will stand out a bit.

The whole thing—and this was the tricky part—is piped to Out-String. You see, ConvertTo-HTML puts a bunch of things into the pipeline—strings, collections of strings, all kinds of wacky stuff. All of that will cause problems later when we try to assemble the final HTML page, so we’re using Out-String to resolve everything into plain old strings.

We can also produce the second fragment now. This is a bit easier because we don’t need to write our function first, but the HTML part will look substantially the same. In fact, the only real difference is that we are assembling our data in a table rather than as a list.

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

         -ComputerName SERVER2 |

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

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

              @{name='FreeSpace(GB)';expression={

              $_.freespace / 1GB -as [int]}} |

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

Out-String

We now have two HTML fragments, $frag1 and $frag2, so we are ready to assemble the final page. Join us tomorrow to see how this is accomplished.

~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
  • Good blog.

  • @Ashish Mahajan thank you for your comment. Today's posting was great; I am glad that you enjoyed it.

  • I'm getting the following, any suggestions?

    Select-Object : Illegal key expresssion

    At line:3 char:10

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

  • NMayberry, you're welcome to post on http://bit.ly/AskDon - that's where my co-authors and I typically handle questions. However, I'm not seeing anything wrong with that syntax as-is, at least not when I'm running it in my Windows 8 virtual machine. It should work fine in PowerShell v2 as well. The entire command, copied from here and pasted right into the shell, ran fine for me.

  • There's a typo here:

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

    There is an extra 's' in 'expression'

  • Ah, good catch Jeff. I've got a little auto-corrector running that must have caught that when I pasted it. I ran it in my other VM and got the error.

    We'll have to make sure we fix that in the chapter - thanks, NMayberry, for being a tech editor!!

  • Great blog. Cant wait to get the book.

  • Abe chutiye, laved kabhi tere baap ne script banaya tha

  • Good post and very useful.

    Q.  Can we Table color also in this code only?

    I want some information in List and some are in table format which is coming but if color added it will Awesome..

  • Got my answer :) it was simple