Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
November 18, 2025

Object Formatting Fun

In this issue:

  • Data as Information
  • Formatting Rules
  • Formatting Concepts
    • One Object per Pipeline
    • Get-FormatData
  • Object Formatting
    • Creating Your Own
    • Update-FormatData
    • Adding Value
    • Format Views
  • Get-CimProcess Formatted
  • Get-LogonInfo Formatted
  • Summary

We've been on a journey to understand objects in PowerShell, and how to elevate them into meaningful and useful things. We always talk about the object nature of PowerShell, but I don't think people consider what they can do to make those objects more useful. This has been the theme of the articles in the last few weeks. Let's pick up where we left off.

Data as Information

I can't stress enough the importance of separating the object from how it is presented. The object you are working with, whether it is from a function or interactive use, is a source of data. This data of numbers and strings is just raw information. It is only when you present that data in a meaningful way that it becomes information. This is something you can use to make decisions, take action, or communicate with others. The data can also be used to feed the next command in a pipelined expression. At the end of the pipeline, any remaining objects are presented to the user, which might not be you, as information.

When you run a command like Get-Process, you get back a collection of process objects. These objects contain a wealth of information about each process, but when you look at the output in the console, you see a neatly formatted table. This table is not the object itself; it's just one way of presenting the data contained within the process objects, ideally making it easier to read and understand.

However, PowerShell is busy behind the scenes deciding how to format these objects for you. It is important that you understand this process so that you can take control of it when necessary.

Formatting Rules

When you run a command like Get-Process, PowerShell examines any objects being written to the success pipeline that need to be displayed to the user. PowerShell looks at the object type to discover the class name. For example, the Get-Process cmdlet returns objects of the type System.Diagnostics.Process. This is the typename you see when you run Get-Process | Get-Member.

PowerShell then looks for formatting rules that apply to that object type. These rules are defined in special XML files with the extension .ps1xml. These files contain definitions for how to format different object types, including tables, lists, and custom views. In Windows PowerShell 5.1 you can discover them in the $PSHome folder. These files follow a naming convention of <description>.format.ps1xml.

Windows PowerShell format files
figure 1

You won't see these files in PowerShell 7. They have been compiled into the PowerShell executable to improve performance. However, we can still use the format file to extend and customize formatting. I'll get to that later.

Formatting Concepts

The formatting files may have multiple entries for each object type. The first one found is almost always the default.

System.Diagnostic.Process views
figure 2

Each entry is defined as a View element. Each view has a Name attribute that identifies it. Common names include Default, Detailed, and Wide. Each view contains one or more TableControl, ListControl, or CustomControl elements that define how to format the object.

Here's some code that should display the default table view for the System.Diagnostics.Process object type from the DotNetTypes.format.ps1xml file.

Get-Content $PSHome\DotNetTypes.format.ps1xml | Select-String "\bSystem.Diagnostics.Process\b" -Context 3,83 | Select -first 1
Default process view
figure 3

As you look at this, you should be able to recognize the table structure you see when you run Get-Process. Each TableColumn element defines a column in the table. The Label attribute defines the column header. Often this is the corresponding property name, but it doesn't have to be. The TableRowEntry defines the value for each column. Often, this is the PropertyName attribute, but you can also use a ScriptBlock to define a calculated value.

>When it comes time to create your custom formatting, you can reference these format.ps1xml files. However, these files are digitally signed by Microsoft so do not attempt to modify them.

PowerShell uses these rules to determine what the default formatting should be for each object type. It might be a table or it might be a list. There is also a Wide view that shows fewer properties in a wider format but I can't think of anything that uses that by default. You should primarily see tables and lists. Technically, there is a custom view type, but that is for special cases. You shouldn't need to worry about custom views for now.

What if PowerShell ends up with an object that isn't covered by these formatting rules? In that case, PowerShell will fall back to a default formatting behavior, which is to display all properties. If there are five or fewer properties, PowerShell will display them in a table format. If there are more than five properties, PowerShell will display them in a list format. This ensures that you always get some form of output, even if there are no specific formatting rules defined for the object type.

Once PowerShell knows if it needs to display a table or a list, the formatting information is passed to the format cmdlets, i.e. Format-Table or Format-List, to generate the final output that you see in the console. However, you can override this behavior by explicitly using these format cmdlets in your commands. The formatting cmdlets write formatting objects which is why format cmdlets should always be at the end of the pipeline. The only thing you can do after a format cmdlet is to send the output to a file or printer. Or, you can format it as a string using Out-String.

One Object per Pipeline

This is also why we stress that functions should only write one type of object to the pipeline. If you mix object types, PowerShell may not be able to determine the correct formatting rules to apply. Generally, the first rule detected will be used for the entire output. Here's a good example:

PS C:\&gt; Get-CimInstance -ClassName win32_bios; Get-Service bits,winrm

SMBIOSBIOSVersion : M2WKT62A
Manufacturer      : LENOVO
Name              : M2WKT62A
SerialNumber      : MJ0D9JCA
Version           : LENOVO - 1620

Status      : Stopped
Name        : bits
DisplayName : Background Intelligent Transfer Service

Status      : Running
Name        : winrm
DisplayName : Windows Remote Management (WS-Management)

I am running two separate commands separated by the semi-colon. However, they share the same pipeline. The default format for the Get-CimInstance command is a table. This means that even though the default format for a service is a table, the list formatting will be applied to the service objects. In this case, the default table properties for the service object are shown as a list.

Get-FormatData

In a previous article I showed you how to use Get-TypeData to explore extended type data. You can use a similar cmdlet, Get-FormatData, to explore the formatting rules that PowerShell uses for different object types.

PS C:\&gt; Get-FormatData System.ServiceProcess.ServiceController

TypeNames                                 FormatViewDefinition
---------                                 --------------------
{System.ServiceProcess.ServiceController} {service, System.ServiceProcess.ServiceController}

PS C:\&gt; (Get-FormatData System.ServiceProcess.ServiceController).FormatViewDefinition

Name                                    Control
----                                    -------
service                                 System.Management.Automation.TableControl
System.ServiceProcess.ServiceController System.Management.Automation.ListControl

These should be shown in the order they are processed, which means the table view called service is the default view.

PS C:\&gt; (Get-FormatData System.ServiceProcess.ServiceController).FormatViewDefinition[0]

Name    Control
----    -------
service System.Management.Automation.TableControl


PS C:\&gt; (Get-FormatData System.ServiceProcess.ServiceController).FormatViewDefinition[0].control

Headers          : {System.Management.Automation.TableControlColumnHeader,
                   System.Management.Automation.TableControlColumnHeader,
                   System.Management.Automation.TableControlColumnHeader}
Rows             : {System.Management.Automation.TableControlRow}
AutoSize         : False
HideTableHeaders : False
GroupBy          :
OutOfBand        : False

This is the same information you see in the .ps1xml file.

PS C:\&gt; (Get-FormatData System.ServiceProcess.ServiceController).FormatViewDefinition[0].control.rows

Columns                     SelectedBy  Wrap
-------                     ----------  ----
{Status, Name, DisplayName}            False
Want to read the full issue?
GitHub Bluesky LinkedIn Mastodon
Powered by Buttondown, the easiest way to start and grow your newsletter.