11 Formatting: And why it’s done on the right

Let’s quickly review. You know that PowerShell cmdlets produce objects and that those objects often contain more properties than PowerShell shows by default. You know how to use gm to get a list of all of an object’s properties, and you know how to use Select-Object to specify the properties you want to see. Up to this point in the book, you’ve relied on PowerShell’s default configuration and rules to determine how the final output will appear on the screen (or in a file, or in hard-copy form). In this chapter, you’ll learn to override those defaults and create your own formatting for your commands’ output.

11.1 Formatting: Making what you see prettier

We don’t want to give the impression that PowerShell is a full-fledged management-reporting tool, because it isn’t. But PowerShell has good capabilities for collecting information, and, with the right output, you can certainly produce reports using that information. The trick is getting the right output, and that’s what formatting is all about.

On the surface, PowerShell’s formatting system can seem easy to use—and for the most part that’s true. But the formatting system also contains some of the trickiest “gotchas” in the entire shell, so we want to make sure you understand how it works and why it does what it does. We’re not just going to show you a few new commands here; rather, we’ll explain how the entire system works, how you can interact with it, and what limitations you might run into.

11.2 Working with the default formatting

Run our old friend Get-Process again, and pay special attention to the column headers. Notice that they don’t exactly match the property names. Instead, each header has a specific width, alignment, and so forth. All that configuration stuff has to come from someplace, right? You’ll find it in one of the .format.ps1xml files that install with PowerShell. Specifically, formatting directions for process objects are in DotNetTypes.format.ps1xml.

Try it Now You definitely want to have PowerShell open so that you can follow along with what we’re about to show you. This will help you understand what the formatting system is up to under the hood.

We’ll begin by changing to the PowerShell installation folder, specifically, where PSReadLine is, and opening PSReadLine.format.ps1xml. PSReadLine is a PowerShell module that provides the experience when you type in a PowerShell console. It adds a bunch of fancy keyboard shortcuts and syntax highlighting, and is customizable. Be careful not to save any changes to this file. It’s digitally signed, and any changes that you save—even a single carriage return or space added to the file—will break the signature and prevent PowerShell from using the file.

PS /Users/jamesp/> cd $pshome/Modules/PSReadLine
PS /Users/jamesp/> code PSReadLine.format.ps1xml

TIP You might get a warning code: The term 'code' is not recognized as a name of a cmdlet, function, script file, or executable program. To fix this, open the command palette and run the following shell command: Shell Command: Install 'code' command in PATH.

Next, find out the exact type of object returned by Get-PSReadLineKeyHandler:

PS /Users/jamesp/> Get-PSReadLineKeyHandler | get-member

Now, follow these steps:

  1. Copy and paste the complete type name, Microsoft.PowerShell.KeyHandler, to the clipboard.

  2. Switch over to Visual Studio Code and press Cmd-F (or Ctrl-F on Windows) to open the Search dialog.

  3. In the Search dialog, paste in the type name you copied to the clipboard. Press Enter.

  4. You should see Microsoft.PowerShell.KeyHandler in the file. Figure 11.1 shows what you should find.

Figure 11.1 Locating the key handler view in Visual Studio Code

What you’re now looking at in Visual Studio Code is the set of directions that govern how a key handler is displayed by default. Scroll down, and you’ll see the definition for a table view, which you should expect because you already know that key handlers display in a multicolumn table. You’ll see the familiar column names, and if you scroll down a bit more, you’ll see where the file specifies which property will display in each column. You’ll see definitions for column widths and alignments too. When you’re finished browsing, close Visual Studio Code, being careful not to save any changes that you may have accidentally made to the file, and go back to PowerShell.

Try it Now You can also get this format data by running the following command. You can mess around with the object you get back, but we won’t be focusing on it.

PS /Users/jamesp/> Get-FormatData -PowerShellVersion 7.1 -TypeName 
 Microsoft.PowerShell.KeyHandler

When you run Get-PSReadLineKeyHandler, here’s what happens in the shell:

  1. The cmdlet places objects of the type Microsoft.PowerShell.KeyHandler into the pipeline.

  2. At the end of the pipeline is an invisible cmdlet called Out-Default. It’s always there, and its job is to pick up whatever objects are in the pipeline after all of your commands have run.

  3. Out-Default passes the objects to Out-Host, because the PowerShell console is designed to use the screen (called the host) as its default form of output. In theory, someone could write a shell that uses files or printers as the default output instead, but nobody has (that we know of).

  4. Most of the Out- cmdlets are incapable of working with standard objects. Instead, they’re designed to work with special formatting instructions. So, when Out-Host sees that it has been handed standard objects, it passes them to the formatting system.

  5. The formatting system looks at the type of the object and follows an internal set of formatting rules (we’ll cover those in a moment). It uses those rules to produce formatting instructions, which are passed back to Out-Host.

  6. Once Out-Host sees that it has formatting instructions, it follows those instructions to construct the onscreen display.

All of this happens whenever you manually specify an Out- cmdlet too. For example, run Get-Process | Out-File procs.txt, and Out-File will see that you’ve sent it some normal objects. It will pass those to the formatting system, which creates formatting instructions and passes them back to Out-File. Out-File then constructs the text file based on those instructions. So the formatting system becomes involved anytime objects need to be converted into human-readable textual output.

What rules does the formatting system follow in step 5? For the first formatting rule, the system looks to see whether the type of object it’s dealing with has a predefined view. That’s what you saw in PSReadLine.format.ps1xml: a predefined view for a KeyHandler object. A few other .format.ps1xml files are installed with PowerShell, and they’re all loaded by default when the shell starts. You can create your own predefined views as well, although doing so is beyond the scope of this book.

The formatting system looks for predefined views that specifically target the object type it’s dealing with. In this case, it’s looking for the view that handles Microsoft .PowerShell.KeyHandler objects.

What if there’s no predefined view? Let’s find out using the System.Uri type, which doesn’t have an entry in a format.ps1xml file (we promise!). Try running this:

[Uri]"https://github.com"

This is using a concept called “casting,” where we say, “Hey, PowerShell, I’ve got this string that looks like a URI. Can you just treat it like the type System.Uri?” And PowerShell replies, “You got it!” and gives you a Uri object. You might notice we didn’t have to specify System in the line that we ran. That’s because PowerShell tacks on System to the front if it can’t find a type just called Uri. Clever PowerShell! Anyway, the output of that is a long list of properties like so:

AbsolutePath   : /
AbsoluteUri    : https://github.com/
LocalPath      : /
Authority      : github.com
HostNameType   : Dns
IsDefaultPort  : True
IsFile         : False
IsLoopback     : False
PathAndQuery   : /
Segments       : {/}
IsUnc          : False
Host           : github.com
Port           : 443
Query          :
Fragment       :
Scheme         : https
OriginalString : https://github.com
DnsSafeHost    : github.com
IdnHost        : github.com
IsAbsoluteUri  : True
UserEscaped    : False
UserInfo       :

The formatting is not too bad for something that doesn’t have any special formatting. That’s because PowerShell will look at the properties of the type and show them in a friendly view. We can control which properties we see here by introducing a format .ps1xml for this type, or we can allow the formatting system to take its next step, or what we call the second formatting rule : It looks to see whether anyone has declared a default display property set for that type of object. You’ll find those in a different configuration file, types.ps1xml. Since we’re not going to dive deep into writing our own format and types files, we’re going to give you one to load in, and we’ll just see how it affects the output. First, let’s create and open up a new file called Uri.Types.ps1xml file in Visual Studio Code:

PS /Users/jamesp/> code /tmp/Uri.Types.ps1xml  

Now, paste in the following content and save the file:

<?xml version="1.0" encoding="utf-8" ?>
<Types>
  <Type>
    <Name>System.Uri</Name>
    <Members>
      <MemberSet>
        <Name>PSStandardMembers</Name>
        <Members>
          <PropertySet>
            <Name>DefaultDisplayPropertySet</Name>
            <ReferencedProperties>
              <Name>Scheme</Name>
              <Name>Host</Name>
              <Name>Port</Name>
              <Name>AbsoluteUri</Name>
              <Name>IsFile</Name>
            </ReferencedProperties>
          </PropertySet>
        </Members>
      </MemberSet>
    </Members>
  </Type>
</Types>

Excellent, now, see the DefaultDisplayPropertySet? Make a note of the five properties listed there. Then go back to PowerShell and run this:

PS /Users/jamesp/> Update-TypeData -Path /tmp/Uri.Types.ps1xml

We’ve just loaded that Types.ps1xml file we just created. Now let’s run the original line again and see what it gets us:

PS /Users/jamesp/> [Uri]"https://github.com"
 
Scheme      : https
Host        : github.com
Port        : 443
AbsoluteUri : https://github.com/
IsFile      : False

Do the results look familiar? They should—the properties you see are there solely because they’re listed as defaults in Types.ps1xml. If the formatting system finds a default display property set, it’ll use that set of properties for its next decision. If it doesn’t find one, the next decision will consider all of the object’s properties.

That next decision—the third formatting rule—is about the kind of output to create. If the formatting system displays four or fewer properties, it uses a table. If there are five or more properties, it uses a list. That’s why the System.Uri object wasn’t displayed as a table: its five properties trigger a list. The theory is that more than four properties might not fit well into an ad hoc table without truncating information.

Now you know how the default formatting works. You also know that most Out- cmdlets automatically trigger the formatting system so that they can get the formatting instructions they need. Next let’s look at how to control that formatting system ourselves and override the defaults.

Oh, and by the way, the formatting system is why PowerShell sometimes seems to “lie.” For example, run Get-Process and look at the column headers. See the one labeled PM(K)? Well that’s a lie, sort of, because there’s no property called PM(K). There’s a property called PM. The lesson here is that formatted column headers are just that—column headers. They aren’t necessarily the same as the underlying property names. The only safe way to see property names is to use Get-Member.

11.3 Formatting tables

PowerShell has four formatting cmdlets, and we’ll work with the three that provide the most day-to-day formatting capability (the fourth is briefly discussed in an “Above and beyond” sidebar near the end of this chapter). First up is Format-Table, which has an alias, ft.

If you read the help file for Format-Table, you’ll notice that it has several parameters. These are some of the most useful ones, along with examples of how to use them:

  • -Property—This parameter accepts a comma-separated list of properties that should be included in the table. These properties aren’t case sensitive, but the shell will use whatever you type as the column headers, so you can get nicer-looking output by properly casing the property names (e.g., CPU instead of cpu). This parameter accepts wildcards, meaning you can specify * to include all properties in the table, or something like c* to include all properties starting with c. Notice that the shell will still display only the properties it can fit in the table, so not every property you specify may display. This parameter is positional, so you don’t have to type the parameter name, provided the property list is in the first position. Try these examples (the second example from the help file for Format-Table is shown here):

    Get-Process | Format-Table -Property *
    Get-Process | Format-Table -Property ID,Name,Responding
    Get-Process | Format-Table *
       Id Name            Responding
       -- ----            ----------
    20921 XprotectService       True
     1242 WiFiVelocityAge       True
      434 WiFiAgent             True
    89048 VTDecoderXPCSer       True
    27019 VTDecoderXPCSer       True
      506 ViewBridgeAuxil       True
      428 usernoted             True
      407 UserEventAgent        True
      544 useractivityd         True
      710 USBAgent              True
     1244 UsageTrackingAg       True
      416 universalaccess       True
      468 TrustedPeersHel       True
      412 trustd                True
    24703 transparencyd         True
     1264 TextInputMenuAg       True
    38115 Telegram              True
      425 tccd                  True
      504 talagent              True
     1219 SystemUIServer        True
  • -GroupBy—This parameter generates a new set of column headers each time the specified property value changes. This works well only when you’ve first sorted the objects on that same property. An example is the best way to show how this works (this one will group Azure VMs based on whether they are running or stopped):

    PS /Users/jamesp/> Get-AzVM -Status | Sort-Object PowerState | 
     ft -Property Name,Location,ResourceGroupName -GroupBy PowerState 
     
       PowerState: VM running
    Name       Location ResourceGroupName
    ----       -------- -----------------
    MyUbuntuVM eastus2  MYUBUNTUVM
     
       PowerState: VM deallocated
    Name        Location ResourceGroupName
    ----        -------- -----------------
    MyUbuntuVM2 eastus2  MYUBUNTUVM
    WinTestVM2  westus2  WINTESTVM2
  • -Wrap—If the shell has to truncate information in a column, it’ll end that column with ellipses (. . .) to visually indicate that information was suppressed. This parameter enables the shell to wrap information, which makes the table longer but preserves all the information you want to display. Here’s an example:

    PS /Users/jamesp/> Get-Command | Select-Object Name,Source | ft -Wrap
     
    Name                                       Source
    ----                                       ------
    Compress-Archive                           Microsoft.P
                                               owerShell.A
                                               rchive
    Configuration                              PSDesiredSt
                                               ateConfigur
                                               ation
    Expand-Archive                             Microsoft.P
                                               owerShell.A
                                               rchive
    Expand-GitCommand                          posh-git
    Find-Command                               PowerShellG
                                               et
    Find-DscResource                           PowerShellG
                                               et
    Find-Module                                PowerShellG
                                               et
    Find-RoleCapability                        PowerShellG
                                               et

Try it Now You should run through all of these examples in the shell, and feel free to mix and match these techniques. Experiment to see what works and what sort of output you can create. These commands will only work if you have already connected to an Azure account and if you have existing virtual machines in Azure.

11.4 Formatting lists

Sometimes you need to display more information than will fit horizontally in a table, which can make a list useful. Format-List is the cmdlet you’ll turn to, or you can use its alias, fl.

This cmdlet supports some of the same parameters as Format-Table, including -Property. In fact, fl is another way of displaying the properties of an object. Unlike gm, fl will also display the values for those properties so that you can see what kind of information each property contains:

Get-Verb | Fl *
...
Verb        : Remove
AliasPrefix : r
Group       : Common
Description : Deletes a resource from a container
 
Verb        : Rename
AliasPrefix : rn
Group       : Common
Description : Changes the name of a resource
 
Verb        : Reset
AliasPrefix : rs
Group       : Common
Description : Sets a resource back to its original state
 
Verb        : Resize
AliasPrefix : rz
Group       : Common
Description : Changes the size of a resource
 
Verb        : Search
AliasPrefix : sr
Group       : Common
Description : Creates a reference to a resource in a container
 
Verb        : Select
AliasPrefix : sc
Group       : Common
Description : Locates a resource in a container
...

We often use fl as an alternative way of discovering the properties of an object.

Try it Now Read the help for Format-List, and try experimenting with its parameters.

11.5 Formatting wide lists

The last cmdlet, Format-Wide (or its alias, fw), displays a wider, multicolumn list. It’s able to display only the values of a single property, so its -Property parameter accepts only one property name, not a list, and it can’t accept wildcards.

By default, Format-Wide looks for an object’s Name property, because Name is a commonly used property and usually contains useful information. The display generally defaults to two columns, but a -Columns parameter can be used to specify more columns:

Get-Process | Format-Wide name -col 4
 
iTerm2        java          keyboardserv... Keychain Ci...
knowledge-ag... LastPass      LocationMenu  lockoutagent
loginwindow   lsd           Magnet        mapspushd
mdworker      mdworker_sha... mdworker_sha... mdworker_sh...
mdworker_sha... mdworker_sha... mdworker_sha... mdworker_sh...
mdworker_sha... mdworker_sha... mdworker_sha... mdwrite
media-indexer mediaremotea... Microsoft Ed... Microsoft E...
Microsoft Ed... Microsoft Ed... Microsoft Ed... Microsoft E...
Microsoft Ed... Microsoft Ed... Microsoft Ed... Microsoft E...

Try it Now Read the help for Format-Wide, and try experimenting with its parameters.

11.6 Creating custom columns and list entries

Flip back to the previous chapter and review section 10.5. In that section, we showed you how to use a hash table construct to add custom properties to an object. Both Format-Table and Format-List can use those same constructs to create custom table columns or custom list entries.

You might do this to provide a column header that’s different from the property name being displayed:

Get-AzStorageAccount | Format-Table @{name='Name';expression=
  {$_.StorageAccountName}},Location,ResourceGroupName
 
Name                     Location       ResourceGroupName
----                     --------       -----------------
myubuntuvmdiag           eastus2        MyUbuntuVM
ismtrainierout           westus         ismtrainierout
cs461353efc2db7x45cbxa2d westus         cloud-shell-storage...
mtnbotbmyhfk             westus         mtnbot
pssafuncapp              westus         pssafuncapp 

Note This will only work if an Azure connection and storage account already exists.

Or, you might put a more complex mathematical expression in place:

Get-Process | Format-Table Name, @{name='VM(MB)';expression={$_.VM / 1MB 
 -as [int]}}

We admit, we’re cheating a little bit by throwing in a bunch of stuff that we haven’t talked about yet. We might as well talk about it now:

  • Obviously, we’re starting with Get-Process, a cmdlet you’re more than familiar with by now. If you run Get-Process | fl *, you’ll see that the VM property is in bytes, although that’s not how the default table view displays it.

  • We’re telling Format-Table to start with the process’s Name property.

  • Next we’re using a special hash table to create a custom column that will be labeled VM(MB). That’s the first part up to the semicolon, which is a separator. The second part defines the value, or expression, for that column by taking the object’s normal VM property and dividing it by 1 MB. The slash is PowerShell’s division operator, and PowerShell recognizes the shortcuts KB, MB, GB, TB, and PB as denoting kilobyte, megabyte, gigabyte, terabyte, and petabyte, respectively.

  • The result of that division operation will have a decimal component that we don’t want to see. The -as operator enables us to change the data type of that result from a floating-point value to, in this case, an integer value (specified by [int]). The shell will round up or down, as appropriate, when making that conversion. The result is a whole number with no fractional component:

    Name            VM(MB)
    ----            ------
    USBAgent          4206
    useractivityd     4236
    UserEventAgent    4235
    usernoted         4242
    ViewBridgeAuxil   4233
    VTDecoderXPCSer   4234
    VTDecoderXPCSer   4234
    WiFiAgent         4255
    WiFiVelocityAge   4232
    XprotectService   4244

We show you this little division-and-changing trick because it can be useful in creating nicer-looking output. We won’t spend much more time in this book on these operations (although we’ll tell you that * is used for multiplication, and as you might expect, + and - are for addition and subtraction, respectively).

Above and beyond

Try repeating this example:

Get-Process |
Format-Table Name,
@{name='VM(MB)';expression={$_.VM / 1MB -as [int]}} -AutoSize

But this time don’t type it all on one line. Type it exactly as it’s shown here in the book, on three lines total. You’ll notice after typing the first line, which ends with a pipe character, that PowerShell changes its prompt. That’s because you ended the shell in a pipe, and the shell knows that more commands are coming. It will enter this same “waiting for you to finish” mode if you press Enter without properly closing all curly brackets, braces, quotation marks, and parentheses.

If you didn’t mean to enter that extended-typing mode, press Ctrl-C to abort, and start over. In this case, you could type the second line of text and press Enter, and then type the third line and press Enter. In this mode, you’ll have to press Enter one last time, on a blank line, to tell the shell you’re finished. When you do so, it will execute the command as if it had been typed on a single, continuous line.

Unlike Select-Object, whose hash tables can accept only a Name and Expression key (although they’ll also accept N, L, and Label for Name, and will accept E for Expression), the Format- commands can handle additional keys that are intended to control the visual display. These additional keys are most useful with Format-Table:

  • FormatString specifies a formatting code, causing the data to be displayed according to the specified format. This is mainly useful with numeric and date data. Go to the documentation on formatting types at http://mng.bz/XWy1 to review the available codes for standard numeric and date formatting and for custom numeric and date formatting.

  • Width specifies the desired column width.

  • Alignment specifies the desired column alignment, either Left or Right.

Using those additional keys makes it easier to achieve the previous example’s results, and even to improve them:

Get-Process |
 Format-Table Name,
 @{name='VM(MB)';expression={$_.VM};formatstring='F2';align='right'} 
 -AutoSize

Now we don’t have to do the division, because PowerShell will format the number as a fixed-point value having two decimal places, and it will right-align the result.

11.7 Going out: To a file or to the host

Once something is formatted, you have to decide where it’ll go. If a command line ends in a Format- cmdlet, the formatting instructions created by the Format- cmdlet go to Out-Default, which forwards them to Out-Host, which displays them on the screen:

Get-ChildItem | Format-Wide

You could also manually pipe the formatting instructions to Out-Host, which accomplishes exactly the same thing:

Get-ChildItem | Format-Wide | Out-Host

Alternatively, you can pipe formatting instructions to Out-File to direct formatted output to a file. As you’ll read in section 11.9, only one of those two Out- cmdlets should ever follow a Format- cmdlet on the command line.

Keep in mind that Out-File defaults to a specific character width for output, which means a text file might look different from an onscreen display. The cmdlet has a -Width parameter that enables you to change the output width, if desired, to accommodate wider tables.

11.8 Another out: GridViews

In the old days of Windows PowerShell, there was a cmdlet that was included called Out-GridView, which provides another useful form of output—a graphical user interface (GUI). For PowerShell 6+, a cross-platform version of this cmdlet was created, but it exists in the PowerShell Gallery in the form of a module. You can install this cmdlet by running

Install-Module Microsoft.PowerShell.GraphicalTools

Note that Out-GridView isn’t technically formatting; in fact, Out-GridView entirely bypasses the formatting subsystem. No Format- cmdlets are called, no formatting instructions are produced, and no text output is displayed in the console window. Out-GridView can’t receive the output of a Format- cmdlet—it can receive only the regular objects output by other cmdlets.

Figure 11.2 shows what happens when we run the command Get-Process | Out-GridView.

Figure 11.2 The results of the Out-GridView cmdlet

11.9 Common points of confusion

As we mentioned at the start of this chapter, the formatting system has most of the gotchas that trip up PowerShell newcomers. They tend to run across two issues, so we’ll try to help you avoid them.

11.9.1 Always format right

It’s incredibly important that you remember one rule from this chapter: format right. Your Format- cmdlet should be the last thing on the command line, with Out-File as the only exception. The reason for this rule is that the Format- cmdlets produce formatting instructions, and only an Out- cmdlet can properly consume those instructions. If a Format- cmdlet is last on the command line, the instructions will go to Out-Default (which is always at the end of the pipeline), which will forward them to Out-Host, which is happy to work with formatting instructions. Try running this command to illustrate the need for this rule:

Get-History | Format-Table | gm
 
   TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
Name                                    MemberType Definition
----                                    ---------- ----------
Equals                                  Method     bool Equals(System.Object  
                                                  obj)
GetHashCode                             Method     int GetHashCode()
GetType                                 Method     type GetType()
ToString                                Method     string ToString()
autosizeInfo                            Property   Microsoft.PowerShell.Commands.Internal.Format.AutosizeInfo, 
    System.Management.Automation, Version=7.0.0.0,...
ClassId2e4f51ef21dd47e99d3c952918aff9cd Property   string 
 ClassId2e4f51ef21dd47e99d3c952918aff9cd {get;}
groupingEntry                           Property   Microsoft.PowerShell.Commands.Internal.Format.GroupingEntry, 
    System.Management.Automation, Version=7.0.0.0...
pageFooterEntry                         Property   Microsoft.PowerShell.Commands.Internal.Format.PageFooterEntry, 
    System.Management.Automation, Version=7.0.0...
pageHeaderEntry                         Property   Microsoft.PowerShell.Commands.Internal.Format.PageHeaderEntry, 
    System.Management.Automation, Version=7.0.0...
shapeInfo                               Property   Microsoft.PowerShell.Commands.Internal.Format.ShapeInfo, 
    System.Management.Automation, Version=7.0.0.0, Cu...
 
   TypeName: Microsoft.PowerShell.Commands.Internal.Format.GroupStartData
Name                                    MemberType Definition
----                                    ---------- ----------
Equals                                  Method     bool Equals(System.Object 
                                                  obj)
GetHashCode                             Method     int GetHashCode()
GetType                                 Method     type GetType()
ToString                                Method     string ToString()
ClassId2e4f51ef21dd47e99d3c952918aff9cd Property   string ClassId2e4f51ef21dd47e99d3c952918aff9cd {get;}
groupingEntry                           Property   Microsoft.PowerShell.Commands.Internal.Format.GroupingEntry, 
    System.Management.Automation, Version=7.0.0.0...
shapeInfo                               Property   
 Microsoft.PowerShell.Commands.Internal.Format.ShapeInfo, 
 System.Management.Automation, Version=7.0.0.0, Cu...

You’ll notice that gm isn’t displaying information about your history objects because the Format-Table cmdlet doesn’t output history objects. It consumes the history objects you pipe in, and it outputs formatting instructions—which is what gm sees and reports on. Now try this:

Get-History | Select-Object Id,Duration,CommandLine | Format-Table | 
ConvertTo-Html | Out-File history.html

Go ahead and open history.html in a browser, and you’ll see some crazy results. You didn’t pipe history objects to ConvertTo-Html; you piped formatting instructions, so that’s what got converted to HTML. This illustrates why a Format- cmdlet, if you use one, has to be either the last thing on the command line or the second-to-last, with the last cmdlet being Out-File.

Also know that Out-GridView is unusual (for an Out- cmdlet, at least), in that it won’t accept formatting instructions and will accept only standard objects. Try these two commands to see the difference:

PS C:>Get-Process | Out-GridView
PS C:>Get-Process | Format-Table | Out-GridView

That’s why we explicitly mentioned Out-File as the only cmdlet that should follow a Format- cmdlet (technically, Out-Host can also follow a Format- cmdlet, but there’s no need because ending the command line with the Format- cmdlet will get the output to Out-Host anyway).

11.9.2 One type of object at a time, please

The next thing to avoid is putting multiple kinds of objects into the pipeline. The formatting system looks at the first object in the pipeline and uses the type of that object to determine what formatting to produce. If the pipeline contains two or more kinds of objects, the output won’t always be complete or useful.

For example, run this:

PS /Users/jamesp/> Get-Process; Get-History
 
 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
...
      0     0.00       1.74       0.25    1244   1 UsageTrackingAg
      0     0.00       0.68       0.19     710   1 USBAgent
      0     0.00       4.12       6.37     544   1 useractivityd
      0     0.00       5.44       8.00     407   1 UserEventAgent
      0     0.00       7.50       3.43     428   1 usernoted
      0     0.00       3.44       8.71     506   1 ViewBridgeAuxil
      0     0.00       5.91       0.08   27019 ...19 VTDecoderXPCSer
      0     0.00       5.92       0.07   89048 ...48 VTDecoderXPCSer
      0     0.00      10.79      50.02     434   1 WiFiAgent
      0     0.00       1.11       0.20    1242   1 WiFiVelocityAge
      0     0.00      10.28       4.30   20921 ...21 XprotectService
 
Id                 : 1
CommandLine        : Update-TypeData -Path /tmp/Uri.Types.ps1xml
ExecutionStatus    : Completed
StartExecutionTime : 9/21/2019 12:20:03 PM
EndExecutionTime   : 9/21/2019 12:20:03 PM
Duration           : 00:00:00.0688690
 
Id                 : 2
CommandLine        : Update-TypeData -Path /tmp/Uri.Types.ps1xml
ExecutionStatus    : Completed
StartExecutionTime : 9/21/2019 12:21:07 PM
EndExecutionTime   : 9/21/2019 12:21:07 PM
Duration           : 00:00:00.0125330eyp

That semicolon allows us to put two commands onto a single command line, without piping the output of the first cmdlet into the second one. This means both cmdlets run independently, but they put their output into the same pipeline. As you see, the output starts out fine, displaying process objects. But the output breaks down when it’s time to display the history objects. Rather than producing the table you’re used to, PowerShell reverts to a list. The formatting system isn’t designed to take multiple kinds of objects and make the results look as attractive as possible.

What if you want to combine information drawn from two (or more) places into a single form of output? You absolutely can, and you can do so in a way that the formatting system can deal with nicely. But that’s an advanced topic that we won’t get to in this book.

Above and beyond

Technically, the formatting system can handle multiple types of objects—if you tell it how. Run Dir | gm, and you’ll notice that the pipeline contains both DirectoryInfo and FileInfo objects (gm has no problem working with pipelines that contain multiple kinds of objects and will display member information for all of them.) When you run Dir by itself, the output is perfectly legible. That’s because Microsoft provides a predefined custom formatting view for DirectoryInfo and FileInfo objects, and that view is handled by the Format-Custom cmdlet.

Format-Custom is mainly used to display various predefined custom views. You could technically create your own predefined custom views, but the necessary XML syntax is complicated and isn’t publicly documented at this time, so custom views are limited to what Microsoft provides.

Microsoft’s custom views do get a lot of usage, though. PowerShell’s help information is stored as objects, for example, and the formatted help files you see on the screen are the result of feeding those objects into a custom view.

11.10 Lab

Note For this lab, you need any computer running PowerShell 7.1 or later.

See if you can complete the following tasks:

  1. Display a table of processes that includes only the process names, IDs, and whether they’re responding to Windows (the Responding property has that information). Have the table take up as little horizontal room as possible, but don’t allow any information to be truncated.

  2. Display a table of processes that includes the process names and IDs. Also include columns for virtual and physical memory usage, expressing those values in megabytes (MB).

  3. Use Get-Module to get a list of loaded modules. Format the output as a table that includes, in this order, the module name and the version. The column headers must be ModuleName and ModuleVersion.

  4. Use Get-AzStorageAccount and Get-AzStorageContainer to display all of your storage containers so that a separate table is displayed for storage containers that are accessible to the public and storage containers that are not. (Hint: Piping is your friend . . . use a -GroupBy parameter.)

  5. Display a four-column-wide list of all directories in the home directory.

  6. Create a formatted list of all .dll files in $pshome, displaying the name, version information, and file size. PowerShell uses the Length property, but to make it clearer, your output should show Size.

11.11 Lab answers

  1. Get-Process | Format-Table Name,ID,Responding -Wrap

  2. Get-Process | Format-Table Name,ID,

    @{l='VirtualMB';e={$_.vm/1MB}},

    @{l='PhysicalMB';e={$_.workingset/1MB}}

  3. Get-Module| Format-Table @{l='ModuleName';e={$_.Name }},

    @{l='ModuleVersion';e={$_.Version}}

  4. Get-AzStorageAccount | Get-AzStorageContainer | ft -GroupBy PublicAccess

  5. gci ~ -Directory | format-wide –column 4

  6. gci $pshome/*.dll |

    Format-List Name,VersionInfo,@{Name="Size";Expression={$_.length}}

11.12 Further exploration

This is the perfect time to experiment with the formatting system. Try using the three main Format- cmdlets to create different forms of output. The labs in upcoming chapters will often ask you to use specific formatting, so you might as well hone your skills with these cmdlets and start memorizing the more-often-used parameters covered in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.204.186