Formatting - PowerShell fundamentals - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 1. PowerShell fundamentals

Chapter 9. Formatting

This chapter covers

· PowerShell’s formatting system

· PowerShell’s Format cmdlets

· Creating custom formats

· Formatting tips

PowerShell, as you’ve learned in the preceding chapters, works primarily with objects. Objects are just an in-memory data structure. But the time comes when PowerShell needs to share information from those objects with us humans. PowerShell has to take those in-memory data structures and convert them into something a person can view. PowerShell’s formatting system is designed to accomplish that task.

9.1. The time to format

Whenever you construct a command line, those commands all run in a pipeline. What you don’t see is an invisible cmdlet hanging out at the end of every pipeline: Out-Default. It’s hardcoded into the shell, and you can’t get rid of it. You also never have to explicitly call it. Its sole purpose is to kick off the formatting process, using whatever objects happen to be in the pipeline at that point.

That’s an important concept, so let’s linger on it for a second. Consider this command line:

Get-Service | Export-CSV –Path services.csv

What output does that command create? You might be tempted to say “a file on disk,” but that’s completely external to PowerShell. Maybe a better question is “What objects does that command leave in the pipeline?” Try running the command, right now, and see what appears on the screen. The answer: nothing. Nothing appears on the screen, because no objects were produced by the command. Get-Service certainly produced objects and put them in the pipeline, but Export-CSV consumed those objects and didn’t put any of its own into the pipeline. So, no objects in the pipeline means Out-Default has nothing to work with, and so that’s what you see on the screen: nothing.

But let’s say you run a command that does leave objects in the pipeline. Those go to Out-Default, which simply passes them on to another cmdlet, Out-Host, that’s invoked by default. You don’t have to do anything. Out-Host can’t make heads or tails of those objects, though, because it only understands a special kind of object we call formatting directives (that isn’t their official name, but it’s one we use a lot because we don’t know if they even have an official name). So when Out-Host gets anything that isn’t formatting directives, it calls on the shell’s formatting system. That system kicks in, extracts data from the objects, and uses the data to create formatting directives. Those are passed back to Out-Host, and output appears on your screen. Figure 9.1 shows all of this in action.

Figure 9.1. How PowerShell turns objects into text

Most of the time you blindly accept the formatting directives. But by understanding the mystery behind them, you can control them.

9.2. The formatting system

Microsoft has provided the formatting system with a few rules, and a lot of configuration defaults, that let it produce decent-looking output without any work on your part. The process involves a few steps, which we’ll cover here.

9.2.1. Is there a predefined view?

Microsoft ships PowerShell with a whole mess of predefined views. You can also create your own views and load them into memory for PowerShell to use; we’ve devoted chapter 26 to showing you how. Look in PowerShell’s installation folder and you’ll see Microsoft’s predefined views:

PS C:\> dir $pshome -filter *.format.ps1xml

Directory: C:\Windows\System32\WindowsPowerShell\v1.0

Mode LastWriteTime Length Name

---- ------------- ------ ----

-a--- 18/06/2013 15:50 27338 Certificate.format.ps1xml

-a--- 18/06/2013 15:50 27106 Diagnostics.Format.ps1xml

-a--- 18/06/2013 15:50 147702 DotNetTypes.format.ps1xml

-a--- 18/06/2013 15:50 14502 Event.Format.ps1xml

-a--- 18/06/2013 15:50 21293 FileSystem.format.ps1xml

-a--- 18/06/2013 15:50 287938 Help.format.ps1xml

-a--- 18/06/2013 15:50 97880 HelpV3.format.ps1xml

-a--- 18/06/2013 19:30 105230 PowerShellCore.format.ps1xml

-a--- 18/06/2013 15:50 18612 PowerShellTrace.format.ps1xml

-a--- 18/06/2013 15:50 13659 Registry.format.ps1xml

-a--- 18/06/2013 15:50 17731 WSMan.Format.ps1xml

These are some of the ones that we found on our Windows 8.1 computers. They’re XML files, and they contain formatting instructions for a wide variety of types of objects. So when PowerShell needs to display a process object, or a service object, or whatever, it looks through these files (which it’s programmed to automatically load each time it starts) to see if that object type is covered by a view. If the object being displayed is covered by a view within one of these files, then that view is used. That’s why running Get-Process, Get-Service, and most other commands produces the output they do: because Microsoft took the time to create that nice-looking display as a predefined view—in other words, a default format.

Predefined views tell PowerShell what kind of layout—such as a table or a list—to use. They tell the shell what properties to display, how wide columns should be, whether data should be left- or right-aligned, and so on.

9.2.2. What properties should be displayed?

If there’s no predefined view, PowerShell looks to see if the object in the pipeline has a DefaultDisplayPropertySet. That’s a special property set, defined as a type extension (we have a chapter on those later in the book, too, chapter 27) in another XML file. As you might expect, Microsoft defines this property set for a lot of object types. Try running this:

Get-WmiObject –Class Win32_OperatingSystem

You’ll see a DefaultDisplayPropertySet at work.

If a DefaultDisplayPropertySet exists, PowerShell will display only those properties. If one doesn’t exist, it’ll display every property that the object has.

9.2.3. List or table?

Based on the number of properties it’s been asked to display, PowerShell chooses between a table layout or a list layout. Tables are used only when there are four or fewer properties, on the theory that the screen should have enough room to display them all. Five or more properties automatically trigger a list layout—although you can override this behavior by explicitly calling the Format cmdlet of your choice.

Keep in mind that this happens only if a predefined view wasn’t found. Process objects, for example, display in an eight-column table because there’s a predefined view that tells the shell to do it that way.

9.3. The Format cmdlets

If you want to display something other than the defaults, you have to do it yourself, by piping your command output to one of PowerShell’s Format cmdlets:

· Format-Wide

· Format-Table

· Format-List

· Format-Custom

Let’s look at these in a bit more detail.

9.3.1. Formatting wide lists

The Format-Wide cmdlet generates a multicolumn list that focuses on a single piece of information. If you don’t tell it differently, it looks for the Name property of the objects it’s displaying, because most objects have a Name property.

PS C:\> Get-Process | Format-Wide

calc conhost

conhost conhost

csrss csrss

dfsrs dfssvc

dllhost dns

dwm explorer

Idle iexplore

iexplore ismserv

lsass lsm

Microsoft.ActiveDirectory.WebServices msdtc

mspaint notepad

notepad powershell

powershell_ise PresentationFontCache

services smss

spoolsv svchost

svchost svchost

svchost svchost

svchost svchost

svchost svchost

svchost svchost

svchost System

taskhost TPAutoConnect

TPAutoConnSvc vds

vmtoolsd vmtoolsd

VMwareTray wininit


Peruse the help for this cmdlet (whose alias is fw, by the way) and you’ll see that it lets you specify the number of columns to display and lets you pick an alternate property. You can also ask it to autosize the display, which tells it to create as many columns as it can while accommodating the data that needs to be displayed. The two parameters –Autosize and –Column are mutually exclusive. Here are some examples:

PS C:\> Get-Process | Format-Wide -Property ID -Column 4

1892 1100 1820 2532

324 372 1288 1468

1016 1324 1964 1788

0 1728 3036 1352

476 484 1248 668

2988 1476 2880 2656

2188 164 468 228

1216 540 640 712

808 848 896 940

980 1408 1792 2388

2828 4 2564 804

1940 1880 1536 2908

2996 364 408

PS C:\> Get-Process | Format-Wide -Property ID –Autosize

1892 1100 1820 2532 324 372 1288 1468 1016 1324 1964 1788 0 1728 3036

1352 476 484 1248 668 2988 1476 2880 2656 2188 164 468 228 1216 540

640 712 808 848 896 940 980 1408 1792 2388 2828 4 2564 804 1940

1880 1536 2908 2996 364 408

Not much to it. We’ll agree that these aren’t the most compelling real-world examples, but they help illustrate the cmdlet. Format-Wide is most useful when you have data to scan through that’s easier to view in a smaller number of rows. The output from Get-Verb (viewing the standard PowerShell verbs) is a good example. The default display is two columns. Using Format-Wide produces a display that’s easier to work with. Try it and see.

9.3.2. Formatting tables

The Format-Table cmdlet (alias ft) is one of the most flexible of the bunch. By default, it formats things in a columnar table display. Note that this has no special effect if it’s displaying a type of object that uses a table by default—you’ll just get the same old thing. But using the –Property parameter, you can choose the properties you want to display. Like Format-Wide, an –AutoSize parameter tries to fit each column to its largest piece of data. Without that, the table will attempt to fill the screen, which can result in an empty-looking display, for example:

PS C:\> Get-Service | Format-Table -Property Name,Status

Name Status

---- ------

ADWS Running

AeLookupSvc Stopped

ALG Stopped

AppIDSvc Stopped

Appinfo Stopped

AppMgmt Stopped

AudioEndpointBuilder Stopped

AudioSrv Stopped

BFE Running


PS C:\> Get-Service | Format-Table -Property Name,Status –AutoSize

Name Status

---- ------

ADWS Running

AeLookupSvc Stopped

ALG Stopped

AppIDSvc Stopped

Appinfo Stopped

AppMgmt Stopped

AudioEndpointBuilder Stopped

AudioSrv Stopped

BFE Running

BITS Running

Browser Stopped

CertPropSvc Stopped

clr_optimization_v2.0.50727_32 Stopped


Quite a difference. Sometimes, it’s possible to include so many columns that some data gets truncated with an ellipsis (...); in those cases, adding the –Wrap parameter will allow data to wrap across multiple rows without being truncated.

Notice that the –Property parameter is positional, so you’ll usually see the command written like this:

PS C:\> Get-Process | ft Name,ID –auto

Name Id

---- --

calc 1892

conhost 1100

conhost 1820

conhost 2532

csrss 324

csrss 372

dfsrs 1288

dfssvc 1468

dllhost 1016

dns 1324

dwm 1964

explorer 1788

Idle 0

iexplore 1728

iexplore 3036


We also used the cmdlet’s alias, ft, and truncated –AutoSize to just –auto or even –a. You’ll see other common shortcuts in the wild.


In PowerShell v2, if you used –AutoSize and PowerShell couldn’t display all the properties because of display width, you’d get a warning that some columns wouldn’t fit and were removed. In PowerShell v3 and later, columns that don’t fit will still be removed, but you won’t get the warning.

There’s also a –GroupBy parameter, which tells the cmdlet to watch a particular property’s values. Every time the value changes, the cmdlet generates a new table header. This can initially seem kind of annoying, as shown by this output excerpt:

PS C:\> Get-Service | Format-Table -GroupBy Status

Status: Running

Status Name DisplayName

------ ---- -----------

Running ADWS Active Directory Web Services

Status: Stopped

Status Name DisplayName

------ ---- -----------

Stopped AeLookupSvc Application Experience

Stopped ALG Application Layer Gateway Service

Stopped AppIDSvc Application Identity

Stopped Appinfo Application Information

Stopped AppMgmt Application Management

Stopped AudioEndpointBu... Windows Audio Endpoint Builder

Stopped AudioSrv Windows Audio

Status: Running

Status Name DisplayName

------ ---- -----------

Running BFE Base Filtering Engine

Running BITS Background Intelligent Transfer Ser...


The trick is to first sort the data on that same property so that the property isn’t flipping back and forth between the same value:

PS C:\> Get-Service | Sort Status | Format-Table -GroupBy Status

Status: Stopped

Status Name DisplayName

------ ---- -----------

Stopped TPVCGateway TP VC Gateway Service

Stopped NtFrs File Replication

Stopped TrkWks Distributed Link Tracking Client

Stopped TrustedInstaller Windows Modules Installer

Stopped NetTcpPortSharing Net.Tcp Port Sharing Service

Stopped PolicyAgent IPsec Policy Agent

Stopped Themes Themes

Stopped THREADORDER Thread Ordering Server

Stopped PerfHost Performance Counter DLL Host


Status: Running

Status Name DisplayName

------ ---- -----------

Running ADWS Active Directory Web Services

Running BFE Base Filtering Engine

Running BITS Background Intelligent Transfer Ser...


There’s also a –HideTableHeaders parameter, which preserves the normal table layout but eliminates the two header lines.

Format-Table wildcard limitation in early PowerShell versions

One thing that may come as a surprise in PowerShell v3 and earlier is that Format-Table has a limitation on the number of properties it can display when using the wildcard for property names. As an experiment, try this code:

Get-Service spooler | Format-Table *

You’ll get no more than 10 properties. Yet if you do this

Get-Service spooler | select *

you can count and see there are more than 10 properties (15 properties exist to be precise). You never get more than 10 properties displayed when using the wildcard with the –Property parameter. As far as we’re aware, this isn’t documented anywhere, but Format-Table appears to be limited to displaying 10 columns.

That said, try this:

Get-Service spooler | Format-Table


Now you’ll get all properties to the limits of your display. You can display more than 10 properties but you have to ask for them.

The wildcard limitation in PowerShell versions v3 and lower probably won’t affect your day-to-day work, but you need to be aware of it when using the wildcard and how to overcome it.

In PowerShell v4 this behavior has changed. Format-Table * will attempt to display all of the properties of the object. To see the effect of this, try:

Get-Process | Format-Table *

Bottom line? You still need to think about what you want to display.

If some of your column properties truncate, you can tell Format-Table to wrap the lines with, what else, -Wrap. Use this with –AutoSize to maximize the amount of formatted data:

PS C:\> Get-Eventlog system -Newest 5 | ft Source,Message -Wrap –auto

Source Message

------ -------

Service Control Manager The start type of the Background

Intelligent Transfer Service service was

changed from auto start to demand start.

Service Control Manager The start type of the Background

Intelligent Transfer Service service was

changed from demand start to auto start.

Microsoft-Windows-Kernel-General The description for Event ID '16' in

Source 'Microsoft-Windows-Kernel-General'

cannot be found. The local computer may

not have the necessary registry

information or message DLL files to

display the message, or you may not have

permission to access them. The following

information is part of the event:'72', '\?


py4\Users\default\ntuser.dat', '496', '27'

Microsoft-Windows-Kernel-General The description for Event ID '15' in

Source 'Microsoft-Windows-Kernel-General'

cannot be found. The local computer may

not have the necessary registry

information or message DLL files to

display the message, or you may not have

permission to access them. The following

information is part of the event:'171', '\


6963}\System Volume Information\SPP\SppCbs



C}', '57552896', '57614336'

Microsoft-Windows-Kernel-General The description for Event ID '16' in

Source 'Microsoft-Windows-Kernel-General'

cannot be found. The local computer may

not have the necessary registry

information or message DLL files to

display the message, or you may not have

permission to access them. The following

information is part of the event:'171', '\


6963}\System Volume Information\SPP\SppCbs



2}', '198', '24'

Finally, like Select-Object, Format-Table supports custom properties (we also refer to them as calculated fields) in its property list. The cool thing is that, unlike Select-Object, Format-Table is explicitly dealing with formatting, so it picks up a few extra keys:

· N or Name and L or Label specify the column header, just as in Select-Object they specify the custom property name. In PowerShell v1 only Label could be used in Format-Table. In PowerShell v2 and later, this changed so Name or Label could be used.

· E or Expression specifies the contents of the column, the same as in Select-Object.

· FormatString, unavailable in Select-Object, applies formatting. For example, N2 is a number with two decimal places. This is .NET string formatting. A good starting reference for the formatting you can use is available at

· Alignment or Align, also unavailable in Select-Object, will accept 'Left', 'Center', or 'Right'.

· Width, also unavailable in Select-Object, lets you specify a column width as a number of characters.

Here’s a one-line example:

PS C:\> get-process | ft Name,ID,@{name='VM';expression={$_.VM /


Name Id VM

---- -- --

conhost 1100 78.89

conhost 1820 46.23

conhost 2532 40.99

csrss 324 43.12

csrss 372 72.04

dfsrs 1288 347.19

dfssvc 1468 34.40

This is an awesome trick. But be sure to read section 9.4 so that you can avoid mistakes folks commonly make when employing this and other formatting tricks.

9.3.3. Formatting lists

After the joy of Format-Table, Format-List (alias fl) may seem a bit mundane. Really, it works a lot like Format-Table. You can specify properties, including custom ones. You can specify * to list all properties, which is a useful trick, especially when you want to quickly bypass the default formatting. It even supports –GroupBy. But there’s no autosizing, and if you construct a custom property you don’t get to specify width or alignment, although FormatString is still legal.

The business of being able to specify and see (technically Format-Table accepts * as a property also, but it’s rarely practical) all properties is a great debugging tool. Although Get-Member will show you all of the properties an object has, Format-List -Property * (or just fl *, which is what folks commonly type) lets you see all of the properties and all of their values. Wondering what the DriveType property is for? Pipe the object to fl * and see what the property contains:

PS C:\> Get-WmiObject win32_logicaldisk -Filter "Deviceid='c:'" | fl *

PSComputerName : QUARK

Status :

Availability :

DeviceID : C:

StatusInfo :

__GENUS : 2

__CLASS : Win32_LogicalDisk

__SUPERCLASS : CIM_LogicalDisk

__DYNASTY : CIM_ManagedSystemElement

__RELPATH : Win32_LogicalDisk.DeviceID="C:"


__DERIVATION : {CIM_LogicalDisk, CIM_StorageExtent, CIM...}


__NAMESPACE : root\cimv2

__PATH : \\QUARK\root\cimv2:Win32_LogicalDisk.Devi...

Access : 0

BlockSize :

Caption : C:

Compressed : False

ConfigManagerErrorCode :

ConfigManagerUserConfig :

CreationClassName : Win32_LogicalDisk

Description : Local Fixed Disk

DriveType : 3

ErrorCleared :

ErrorDescription :

ErrorMethodology :

FileSystem : NTFS

FreeSpace : 127647277056

InstallDate :

LastErrorCode :

MaximumComponentLength : 255

MediaType : 12

Name : C:

NumberOfBlocks :

PNPDeviceID :

PowerManagementCapabilities :

PowerManagementSupported :

ProviderName :

Purpose :

QuotasDisabled :

QuotasIncomplete :

QuotasRebuilding :

Size : 201504845824

SupportsDiskQuotas : False

SupportsFileBasedCompression : True

SystemCreationClassName : Win32_ComputerSystem

SystemName : QUARK

VolumeDirty :

VolumeName :

VolumeSerialNumber : B0CEF5BA

Scope : System.Management.ManagementScope

Path : \\QUARK\root\cimv2:Win32_LogicalDisk.Devi...

Options : System.Management.ObjectGetOptions

ClassPath : \\QUARK\root\cimv2:Win32_LogicalDisk

Properties : {Access, Availability, BlockSize, Captio...}

SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNAS...}

Qualifiers : {dynamic, Locale, provider, UUID}

Site :

Container :

Sometimes it’s just easier to read a list than a table. But there’s another hidden gem with Format-List as well as Format-Table.

9.3.4. Same objects, different formats

Sometimes PowerShell has a few surprises. Here’s one:

PS C:\> Get-Process -id $pid | Format-Table

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName

------- ------ ----- ----- ----- ------ -- -----------

562 15 82660 78432 232 22.39 1516 powershell

That looks pretty normal. The default format output for a process object is a table, so explicitly piping to Format-Table gives the same expected result. Now watch what happens when you pipe the same command to Format-List:

PS C:\> Get-Process -id $pid | Format-List

Id : 1516

Handles : 589

CPU : 22.464144

Name : powershell

The same object but different properties are presented, depending on the format. This happens because, in PowerShell’s format type extension files for the Process object, there are different default property sets, depending on whether you use a list or a table. You might be surprised at what information is readily available. The only way to know for sure is to pipe a command to a Format cmdlet that isn’t what you normally see.

At some stage you may need more control over the format of output data. You can create your own custom formats.

9.3.5. Custom formatting

For all intents and purposes, you won’t use Format-Custom much, if at all. Sure, you can pipe things to it, but by default all it does is show you a complete breakdown of each object’s properties, enumerating through collections and so forth. The cmdlet is primarily designed to work along with predefined custom views, like the one PowerShell uses to construct directory listings or its own help displays. That said, you can create your own custom configuration file and then use it with Format-Custom. We cover formatting extensions in chapter 26 but in the meantime listing 9.1 shows a sample file.

Listing 9.1. Demo-CustomFormat.ps1xml

<?xml version="1.0" encoding="utf-8" ?>









<!-- ################ CUSTOM DEFINITIONS ################ -->









<Text> (</Text>









<Text>FileVersion: </Text>







<Text>Modified Age: </Text>



((Get-Date) –





<Text>Created: </Text>






<Text> Modified: </Text>







<Text>SizeBytes: </Text>





<Text>Owner: </Text>



($_ | Get-ACL).Owner













You’ll need to jump to chapter 26 if you want to understand how it’s constructed. This file defines a custom view, System.Io.FileInfo, for file objects. Once you have this file, you need to import it into your PowerShell session:

PS C:\> Update-FormatData -AppendPath C:\scripts\Demo-FormatCustom.ps1xml

Once it’s loaded, you can format the display of any file object by piping it to Format-Custom:

PS C:\> dir c:\work -file | Format-Custom

C:\work\a.ps1 (Archive)


Modified Age: 18.00:30:41.0124823

Created: 12/6/2013 1:33:17 PM Modified: 12/6/2013 1:37:16 PM

SizeBytes: 349

Owner: BUILTIN\Administrators

C:\work\ComputerData.xml (Archive)


Modified Age: 18.21:48:15.1495274

Created: 12/5/2013 2:12:22 PM Modified: 12/5/2013 4:19:42 PM

SizeBytes: 228

Owner: BUILTIN\Administrators

C:\work\Install.txt (Archive)


Modified Age: 1323.12:28:53.2247604

Created: 5/11/2010 1:39:04 AM Modified: 5/11/2010 1:39:04 AM

SizeBytes: 1639

Owner: JH-WIN81-ENT\Jeff

C:\work\mydata.xml (Archive)


Modified Age: 18.04:35:36.7160348

Created: 12/6/2013 8:45:48 AM Modified: 12/6/2013 9:32:20 AM

SizeBytes: 1352

Owner: BUILTIN\Administrators

C:\work\Server1.mof (Archive)


Modified Age: 18.00:18:51.0901805

Created: 12/6/2013 1:49:06 PM Modified: 12/6/2013 1:49:06 PM

SizeBytes: 1670

Owner: BUILTIN\Administrators

C:\work\WMIExplorer.exe (Archive)

FileVersion: 1.1.3910.1261

Modified Age: 582.00:35:42.2588165

Created: 12/24/2013 1:45:01 PM Modified: 5/21/2012 1:32:15 PM

SizeBytes: 462848

Owner: BUILTIN\Administrators

Because the custom format works with file objects only, our expression only gets file objects. Piping it to Format-Custom gives you a custom view of the file object with the information you need for reporting purposes. You can also create named views to provide custom formatting; you’ll learn about that in chapter 26.

9.4. Eliminating confusion and “gotchas”

Finally, we’re getting far enough along that we need to try to stop you from making some of the same confusing mistakes that we’ve seen our students sometimes make. There are three of them we want to cover.

9.4.1. Formatting is the end of the line

In this section, we aim to make sure you don’t get caught by what’s probably one of PowerShell’s biggest traps. When you see a cmdlet named Format-Table, you probably assume that it formats things into a table form, right? And if your goal is to have, say, an HTML table, then you should be able to do this:

PS C:\> Get-Service | Format-Table –Property Name,Status |

ConvertTo-HTML | Out-File services.html

Go ahead and try that. Don’t worry, you won’t break anything, but the final HTML won’t be very attractive. Why not? Remember that PowerShell’s formatting system—including the Format cmdlets—produce formatting directives, not normal objects. Those formatting directives are intended to be used to construct an onscreen display, or a plain-text file, or a piece of paper. The directives can only be used for those purposes, and no other cmdlet can make any sense of them.

So, to stay out of trouble, just remember two simple rules (well, one rule and one exception):

· If you use a Format cmdlet, it needs to be the last command on your command line.

· The only exceptions are the Out-File, Out-Printer, and Out-Host cmdlets. Because Out-Host is the default, you’ll probably never type it, leaving you with just Out-File and Out-Printer. Those are the only commands that can come after a Format cmdlet.

Follow those rules, and you’ll be fine. Don calls this the “Format Right” rule, meaning you want to move your formatting as far to the right—toward the end—of the command line as possible.

9.4.2. Select or format?

You’ve probably noticed that both Select-Object and the Format cmdlets let you specify the properties you want to see (just one for Format-Wide, but multiple properties are accepted by the other two). You’ll also notice that Select-Object, Format-List, and Format-Table all support the same custom property syntax.

So which one do you use? It depends on what you want to do:

· If you’ve finished manipulating your objects and you’re ready to display them, it’s probably easier to select properties, and make custom properties, using Format-Table or Format-List. Keep in mind, though, that the output from that point has to go to the screen, to a plain-text file, or to a printer.

· If you haven’t finished manipulating your objects, then use Select-Object. Keep in mind that it isn’t dealing with formatting, so it doesn’t have the ability to set column widths, alignment, or formatting strings. But the output can be sent on to other cmdlets.

Sometimes you’ll end up using both, or you’ll end up needing to be very clever. For example, suppose you want to query a computer and get information about its local disk drives. You want to display each drive’s drive letter, total size in gigabytes, and free space in gigabytes. You want both of those values to carry two decimal places. You want to display only drives whose free space is less than 20 percent of the total space.

This is like one of those logic puzzles, where you’re told that Jill lives in a red house, Joe lives next door to Jill, and Kim lives in a small house, and then you have to figure out who lives where. Here are the facts:

· You can use WMI to query the drive information. WMI is covered in chapter 39, so we won’t focus on it now. It works a lot like the other Get cmdlets you’ve seen.

· You need to filter out the drives that you don’t want displayed. This is best done as early as possible in the command line.

· You have some formatting requirements (two decimal places) that Format-Table can accommodate, but you need to decide if that’ll be the last thing you want to do with the objects.

Here’s one example of how you could construct the command:

Get-WmiObject -Class Win32_LogicalDisk -Filter "Drivetype=3" |

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

@{name='Size';expression={$_.Size / 1GB}},

@{name='FreeSpace';expression={$_.FreeSpace / 1GB}},

@{name='PercentFree';expression={$_.FreeSpace / $_.Size * 100}} |

Where-Object { $_.PercentFree -lt 20 } |

Format-Table DriveLetter,


@{name='FreeSpace';FormatString='N2';expression={$_.FreeSpace}} -auto

Let’s walk through this. You start by getting your WMI objects. Easy enough. Then, you use Select-Object to do the math needed to generate values in gigabytes, rather than the default bytes provided by WMI. Then, you filter out the drives you don’t want by using Where-Object. Finally, you need to use Format-Table, because it’s the only thing that can do the formatting necessary to get just two decimal places.

Was this the easiest way? Probably not. Here’s a better approach:

Get-WmiObject -Class Win32_LogicalDisk -Filter "Drivetype=3" |

Where-Object { ($_.FreeSpace / $_.Size) -lt .2 } |

Format-Table @{name='DriveLetter';expression={$_.DeviceID}},

@{name='Size';expression={$_.Size / 1GB};FormatString='N2'},

@{name='FreeSpace';expression={$_.FreeSpace / 1GB};FormatString='N2'} -auto

This example shortens the command line to just three commands. You’re filtering much earlier in the process, which is always good and means this expression runs a bit faster than the first example. Next, there’s no reason to convert the values to gigabytes before doing that filtering, because you can do the division operation with bytes just as easily. You don’t need to make a “PercentFree” column at all, because you don’t want that in your output. You only have to go through the ugly custom-property–making syntax once, at the very end, where it can also handle your formatting instructions. Remember, though, that all this will do is send pretty output to the screen or a text file, assuming you piped to Out-File. You can’t do anything else with it.

So it’s largely a matter of being careful and clever and spending some time thinking about what’s happening and what you want to achieve.

9.4.3. Format, out, export—which?

By this point in this book, we’ve thrown at you a lot of different ways to get information in and out of the shell:

· Import-CSV and Export-CSV

· Get-Content

· Out-File

· Format-Table

If you’re following along, you’re probably starting to wonder what the difference is. We certainly see classroom students trying to run stuff like this:

Get-Content data.csv | Where { $_.Column1 –eq 'value' } |

Select Column1,Column2,Column3

And that just won’t work. Here’s the deal:

· Get-Content and Out-File both work with plain, unstructured text. If you use Get-Content to read in a CSV file, it doesn’t attempt to interpret the file or break apart its contents. It just spews the text onto the screen, including the column headers. You can’t treat that as data objects. Out-File doesn’t do any conversion of the data; what appears in the file is exactly what would’ve otherwise appeared on the screen.

· Import-CSV and Export-CSV deal with structured data. Export-CSV takes objects, which is one kind of data structure, and transforms them into CSV, which is another kind of data structure. Import-CSV does the opposite. If you want to be able to work with columns as properties, you use these.

· Format cmdlets discard your data structure and instead create a visual display that’s only meaningful to humans. There’s no practical way to bring that information back into the shell in any usable form.

We know, it’s a lot to keep track of, but that’s the price of being a PowerShell guru!

9.5. Summary

Formatting is one of the great powers of PowerShell. With a bit of planning and cleverness, you can produce incredible-looking reports, send them out to files or to a printer, or just look at them onscreen—all without much effort. There are some caveats and gotchas involved, and we’ve tried to cover those thoroughly for you here. Practice is the best thing for formatting, so jump in and start seeing what you can do.