Handle Formatted Output in PowerShell

Handle Formatted Output in PowerShell

  • Comments 3
  • Likes

Summary: Microsoft Windows PowerShell MVP, Doug Finke, illustrates how to handle formatted output in a Windows PowerShell script.

Microsoft Scripting Guy, Ed Wilson, is here. Doug Finke is our guest blogger.

Photo of Doug Finke

Doug Finke is a Microsoft PowerShell MVP working for Lab49, a company that builds advanced applications for the financial service industry. Over the last 20 years, Doug has been a developer and author working with numerous technologies. You can catch up with Doug on his blog.

Doug's contact information:
Blog: Development in a Blink: Researching the optimal; implementing the practical

There are four Windows PowerShell cmdlets that handle formatting.

Get-Command format* -CommandType cmdlet

Name

Description

Format-Custom

Formats the output of a command as defined in an alternate view

Format-List

Formats the output of a command as a list of properties in which each property is displayed on a separate line

Format-Table

Formats the output of a command as a table with the selected properties of the object in each column

Format-Wide

Formats objects as a wide table that displays only one property of each object

At the command line, these cmldlets are super helpful. I pipe Windows PowerShell output to Format-Table –Autosize ( ft –a ) all the time. Here is a Format-Table nicety that uses wild cards:

Get-Process | select -First 5  | ft -a name, *paged*mem*size

And here is the output.

Name    NonpagedSystemMemorySize PagedMemorySize PagedSystemMemorySize PeakPagedMemorySize

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

chrome                     19800        54726656                166860            75530240                                                           

chrome                     24372        66035712                312252            81887232

chrome                     12720        55017472                167940            90001408

conhost                     3420         1032192                110476             1060864

conhost                     3480         1810432                110948             1835008

The impact

The challenge: Although this is great, and it makes working with output quick and easy, it has a negative impact on your scripts and functions that you provide to others.

Take this line of Windows PowerShell script. It prints 1 through 5 to the console, simple and problem free.

1..5 | Format-Table

Now, double each element.

1..5 | Format-Table | foreach { $_ * 2 }

I get the following error message:

Method invocation failed because [Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData] doesn't contain a method named 'op_Multiply'.

At line:3 char:37

+ 1..5 | Format-Table | foreach { $_ * <<<<  2}

    + CategoryInfo          : InvalidOperation: (op_Multiply:String) [], RuntimeException

    + FullyQualifiedErrorId : MethodNotFound

This is what users of my scripts and functions will encounter if they try to further act on the output of utilities if I use a Format* cmdlet to output results.

Why, and how do I fix it?

First, I want to find out what is the output from Format-Table. I pipe it to Get-Member. Here are the results:

1..5 | Format-Table | Get-Member

   TypeName: Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData

 

Name                                    MemberType Definition                                                                             

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

Equals                                  Method     bool Equals(System.Object obj)                                                         

GetHashCode                             Method     int GetHashCode()                                                                      

GetType                                 Method     type GetType()                                                                         

ToString                                Method     string ToString()                                                                      

ClassId2e4f51ef21dd47e99d3c952918aff9cd Property   System.String ClassId2e4f51ef21dd47e99d3c952918aff9cd {get;}                           

formatEntryInfo                         Property   Microsoft.PowerShell.Commands.Internal.Format.FormatEntryInfo formatEntryInfo {get;set;}

outOfBand                               Property   System.Boolean outOfBand {get;set;}                                                    

writeErrorStream                        Property   System.Boolean writeErrorStream {get;set;}

This explains why I get errors when I try to multiply the items by 2 when I pipe it to the foreach. The FormatEntryData doesn’t contain a method op_Multiply. Windows PowerShell attempts to multiply FormatEntryData by the integer 2. Windows PowerShell has no built-in definition for this operation, so as a last step, it looks to see whether FormatEntry defines a method for performing the operation.

Windows PowerShell looks for the op_<operation> methods in FormatEntry if the operation is not one of those that are defined by Windows PowerShell. (For more information, see Windows PowerShell in Action, 2nd Edition.)

One way to “fix” it

I most often want the “raw” data from my scripts and functions, and sometimes some nicely formatted output when I am debugging. Here is one way I accomplish this:

function Get-Numbers {

    param([Switch]$Format)

   

    $result = 1..5

   

    if($format) {

        return $result | Format-Table -AutoSize

    }

   

    $result

}

I set up $Format as a switch parameter, which is one that does not require a value and is off by default. I can call this function with the Format parameter, and my output will be piped to Format-Table. That means FormatEntryData objects will be emitted, and I will not be able to operate on the underlying data.

If I call Get-Numbers without the parameter (the default), I can work directly on the numbers that are output, by piping it to any other Windows PowerShell cmdlet (or my functions) such as, Where-Object, ForEach-Object, or Group-Object.

Solve it another way

I find it productive to leverage the modular capabilities in Windows PowerShell. In the following example, I am using a range of numbers; this concept can be applied to any set of objects in Windows PowerShell. I am creating a dynamic module that exists only in memory with New-Module. This script can be saved as a .psm1 file and stored for later use with the Import-Module cmdlet.

Users of this script can now get to any core components of the data, and they get “helper” functions too. For example, it is simple to get the sum of all odd numbers by using Get-SumOfOdd. Plus, this approach enables the flexibility to filter the data in other ways, for example, getting only the numbers that are divisible by 5. Users can also reuse my implementation of Sum as shown here, which in itself could be a custom calculation.

Get-MySum ( Get-Numbers | Where { $_ % 5 -eq 0} )

Dynamic module with New-Module

New-Module -Name TestModule {

   

    function Get-Numbers { 1..10 }

   

    function Get-MySum ($numbers) { $numbers | foreach { $s=0 } { $s+=$_ } { $s } }

    function Get-MyEvenNumbers    { Get-Numbers | Where { $_ % 2 -eq 0 } }

    function Get-MyOddNumbers     { Get-Numbers | Where { $_ % 2 -eq 1 } }   

    function Get-SumOfEven        { Get-MySum (Get-MyEvenNumbers) }

    function Get-SumOfOdd         { Get-MySum (Get-MyOddNumbers)  }

   

} | Import-Module

Example of usage:

@"

Get-Numbers:       $(Get-Numbers)

Get-MyEvenNumbers: $(Get-MyEvenNumbers)

Get-MyOddNumbers:  $(Get-MyOddNumbers)

Sum All Numbers:   $(Get-MySum (Get-Numbers))

Get-SumOfEven:     $(Get-SumOfEven)

Get-SumOfOdd:      $(Get-SumOfOdd)

"@

Results:

Get-Numbers:       1 2 3 4 5 6 7 8 9 10

Get-MyEvenNumbers: 2 4 6 8 10

Get-MyOddNumbers:  1 3 5 7 9

Sum All Numbers:   55

Get-SumOfEven:     30

Get-SumOfOdd:      25

It is important to keep in mind that the scripts and functions I write can be consumed by others (as well as by me). Letting the underlying objects flow across the pipeline is key to leveraging the Windows PowerShell system. I can always provide a parameter or additional function that handles reporting if my user needs it. This is demonstrated with the New-Module example.

A key benefit is that users can always inject a pipeline between the data generation and reporting functions. This allows them to subset the information, say with a Where-Object, or to perform a calculation and then let it flow to the reporting. This provides maximum flexibility.

At the end of the day, outputting formatted data will frustrate your efforts because this is the end of the road for the underlying data. It cannot be transformed or operated on anymore. Outputting objects is the cornerstone of being productive in Windows PowerShell.

Thank you, Doug, for this great blog.

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
  • Great article, Doug

    lesson learned! Input and output objects to keep the pipeline going.

    Formatted output might be a last step to present data.

    Btw: The in memory module is a cool thing ... something to remember!

    Thanks, Klaus.

  • Hey Scripting Guy!

    I absolutely love the Scripting Guy Blog, it's always crammed full of great ideas and fresh in sites into Powershell.

    I recently brought a Kindle from Amazon and was ecstatic when I found SGB was available on Kindle Blogs. Imagine having SGB delivered wireless to my Kindle ready for me to read instantly, how great does that sound?

    I was then mortified when I found out that this isn't available outside of America! I couldn't believe it.

    Please sort this out, I'm sure you will have loads of support from everyone across the pond

    regards

    ZeniTimes

  • thank you