Windows PowerShell Looking Good

Don Jones

It's not uncommon to want your scripts to produce nice-looking output, and I'm often asked about the best way to produce things like tables. There are really a couple of ways to go about doing this. The first step is to write a script that puts the data—that is, whatever data it is that I want to display in a formatted fashion—into variables named

$data1, $data2, and $data3. Now I can get down to formatting the data.

The Text Way

The most common way folks tend to approach this task is the same way they'd handle it if they were working in a language such as VBScript, Perl, KiXtart, or another text-oriented language. I might start by writing out a set of column headers to the screen:

Write-Host "ColumnA'tColumnB'tColumnC"

Note that the `t is a special escape sequence in Windows PowerShellTM that inserts a tab character. To write out each line of my table—remembering that my data is in $data1, $data2, and $data3—I have a few choices. One is to simply write the variables, relying on the shell's ability to replace variables with their contents inside double quotation marks:

Write-Host "$data1't$data2't$data3"

Another slightly more attractive way to achieve this uses the –f operator. This technique separates the formatting from the data and makes what I think is an easier-to-read and more easily maintained line of code:

Write-Host "{0}'t{1}'t{3}" –f $data1,$data2,$data3

This entire approach has significant problems, though. First, by relying on the fixed tab stops in the console window, I am setting myself up for some odd formatting. For example, if a particular table row has a 20-character value in the first column, I throw the entire formatting for that row out of whack. Also, since I'm outputting this text by using Write-Host, I'm pretty much limited to a console display.

There's no easy way to send that output to a file or to put it in other formats, should I ever want to do so. Most importantly, this text-based approach completely ignores the inherently object-based shell that I'm working in, failing to take advantage of all the incredible techniques and capabilities that Windows PowerShell offers.

Video (no longer available)

Watch Don Jones demonstrate how you can easily add formatting to your Windows PowerShell output.

The Windows PowerShell Way

Windows PowerShell is an object-oriented shell. That means, ideally, everything you work with should be in objects, allowing the shell to turn things into text displays when needed. But how do you create objects for arbitrary pieces of data?

Continuing my example, I start by creating a blank custom object and storing it in a variable:

$obj = New-Object PSObject

This gives me a fresh, empty object to work with. I then add my data to the object in the form of properties. To do this, I simply pipe my object to the Add-Member cmdlet. I add something called a NoteProperty, give the property a name (a good idea is to use the column headers as property names), and then insert the data as the values for the properties:

$obj | Add-Member NotePropertyColumnA $data1 $obj | Add-Member NotePropertyColumnB $data2 $obj | Add-Member NotePropertyColumnC $data3

Now I simply output this object—not to the console, but to the pipeline:

Write-Output $obj

I can then repeat these steps for each table row that I need to output. The following is a short function that accepts a string and outputs an uppercase and lowercase version, along with the original string:

functionStringVersions { param([string]$inputString) $obj = New-Object PSObject $obj | Add-Member NoteProperty Original($inputString) $obj | Add-Member NoteProperty Uppercase($inputString.ToUpper()) $obj | Add-Member NoteProperty Lowercase($inputString.ToLower()) Write-Output $obj } $strings = @("one","two","three") foreach ($item in $strings) { StringVersions $item }

I start with an array of string variables, going through each of them one at a time and sending them to the function. And the function's output is written to the pipeline.

You should note that there are shorter ways to write this code, but I chose this technique because it clearly illustrates the point I want to make. The result, as shown in Figure 1, is a perfectly formatted table. That's because the shell already knows how to format objects in a table.

Figure 1 Windows PowerShell output displayed in a table

Figure 1** Windows PowerShell output displayed in a table **(Click the image for a larger view)

Whenever an object has four or fewer properties, Windows PowerShell chooses a table automatically. So with no work whatsoever on my part, I now have a great-looking table.

But Wait, There's More!

Cmdlet of the Month: Get-Command

I'm sure you've used Get-Command, or its handy alias (gcm), once or twice to review the list of available Windows PowerShell cmdlets. But you may not know how flexible gcm really is. For instance, if you want to see everything Windows PowerShell can do with a service, run gcm -noun service. Or, if you want to see all of the Windows PowerShell export options, try gcm -verb export. If you just need to see what cmdlets were added by a particular snap-in, such as the PowerShell Community Extensions, try gcm -pssnapin pscx. (You can replace "pscx" with any snap-in name to see that snap-in's cmdlets.)

As you can see, Get-Command is a key player in the discoverability of Windows PowerShell. It allows you to learn what functionality is available without even having to pick up a manual. And note that gcm is a more accurate way of discovering functionality than using a command such as Help *. The Help function only lists available help topics. Any cmdlets that didn't ship with help don't show up in the help listing—even though the cmdlets are there if you need them.

The benefit of this technique goes far beyond tables. When you're working with objects, Windows PowerShell knows how to do a huge range of things. Want your data in a CSV file? Use Export-CSV. Prefer an HTML table? Pipe your objects to ConvertTo-HTML. Need a list format? Pipe them to Format-List. By using objects, you can make use of all the things the shell already knows how to do. Here's a revised example:

functionStringVersions { PROCESS { $obj = New-Object PSObject $obj | Add-Member NoteProperty Original($_) $obj | Add-Member NoteProperty Uppercase($_.ToUpper()) $obj | Add-Member NoteProperty Lowercase($_.ToLower()) Write-Output $obj } }

This time around, I've modified the function to accept pipeline input—this is always a better idea when you're working with objects—and to output to the pipeline.

This function now has its code inside of a PROCESS scriptblock. This means that function will accept pipeline input and will execute the PROCESS scriptblock once for each object that is piped in.

Within the PROCESS scriptblock, the special $_ variable refers to the current pipeline object that's being processed. The practical result of this is that now I can simply pipe in an array of strings:

@("one","two","three") | StringVersions

I get a table because the function is putting its output into the pipeline. At the end of the pipeline, the shell knows to call on its formatting subsystem, which makes the decision to use a table because the objects in the pipeline have fewer than five properties (for more properties, the shell will use a list by default).

But I don't have to rely on the default behavior. I can simply pipe those objects to another cmdlet to have something else done with them:

@("one","two","three") | StringVersions | Format-List @("one","two","three") | StringVersions | ConvertTo-HTML | Out-File "strings.html" @("one","two","three") | StringVersions | Export-CSV "strings.csv" @("one","two","three") | StringVersions | Select Uppercase,Lowercase -unique

Figure 2 shows the resulting HTML from the second example displayed in a Web browser. By simply representing my output data in objects, rather than representing it as simple text, I suddenly now have complete access to a wealth of functionality that is built into the shell—a variety of formatting layouts; HTML conversion; export options; the ability to sort, filter, and group; and more.

Figure 2 Windows PowerShell data output in HTML format

Figure 2 Windows PowerShell data output in HTML format (Click the image for a larger view)

This is very powerful stuff. In fact, I will go so far as to suggest that every script you write should produce an object as its output so you can use that output in as many different ways as possible—all without writing a single additional line of code.

Don Jones is an expert in Windows administrative automation and has authored such books as Windows PowerShell: TFM and VBScript, WMI, and ADSI Unleashed. You can reach him through the forums on ScriptingAnswers.com.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.