Learn about Windows PowerShell
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.
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
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
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 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
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.
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;}
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.)
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 }
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.
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} )
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
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) "@
@"
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
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
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