Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

PowerShell Format Files the Easy Way

Posted on February 18, 2019February 18, 2019

Whenever I teach or present on PowerShell scripting, I'm always talking about writing objects to the pipeline. Most of the time you can simply let PowerShell format and display output of your command to the best of its ability. However, you may wish to take matters into your own hands and create custom output. For example, when your run Get-Process PowerShell displays a formatted table. But you know that the display is not necessary the underlying object that you see with Get-Member. PowerShell defines a default view for Process objects. You can do the same thing with your commands.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Creating Custom Output

Here's a relatively simple script that writes a custom object to the pipeline with properties derived from a number of WMI classes.

Function Get-SysInfo {

    [cmdletbinding()]

    Param(
        [Parameter(Position = 0, ValueFromPipeline)]
        [ValidateNotNullorEmpty()]
        [string]$Computername = $env:computername,
        [PSCredential]$Credential
    )

    Begin {
        $cimParams = @{
            Classname   = ""
            CimSession  = ""
            Property    = "*"
            ErrorAction = "stop"
        }
    }

    Process {
        Try {
            $sess = New-CimSession @PSBoundParameters
            $cimParams.cimSession = $sess
        }
        Catch {
            Throw $_
        }

        $cimParams.Classname = "Win32_Operatingsystem"
        $cimParams.Property = "CSName", "Caption","Version"
        Try {
            $os = Get-CimInstance @cimParams
        }
        Catch {
            Throw $_
        }
        $cimParams.Classname = "Win32_Computersystem"
        $cimParams.Property = "Manufacturer", "Model"
        $cs = Get-CimInstance @cimParams

        $cimParams.Classname = "Win32_Process"
        $cimParams.Property = "Name"
        $procs = Get-CimInstance @cimParams

        $cimParams.Classname = "Win32_Service"
        $cimParams.Filter = "State = 'running'"
        $running = Get-Ciminstance @cimParams

        [PSCustomobject]@{
            PSTypeName   = "mySysInfo"
            Computername = $os.CSName
            OS           = $os.Caption
            Version      = $os.Version
            System       = ("{0} {1}" -f $cs.Manufacturer.Trim(), $cs.Model.Trim())
            Services     = $Running.Count
            Processes    = $Procs.count
        }

        $cimParams.remove("filter")
        Remove-CimSession $sess

    } #close Process
    End {
        #not used
    }
} #close function

Sample default output

I'm defining a custom type name as part of the custom object.

Viewing the custom object

Let's say I prefer the default output to be a table instead of a list. This will require a table definition stored in a format.ps1xml file. These files can be tricky to create. I used to find something in an existing file like $PSHome\DotnetTypes.format.ps1xml and copy it into a new file which I then updated. But now I have a much better solution.

New-PSFormatXML

My PSScriptTools module (which you can install from the PowerShell Gallery) now includes a command called New-PSFormatXML. The command is designed to analyze an object and by default create a table view of all properties, although you can specify which properties to include. The format.ps1xml file will autosize the table but you can remove the directive and use the widths which are best guesses. Expect some trial and error when defining a new view.

Creating a new file is as easy as this:

Get-SysInfo | New-PSFormatXML -Path .\mySysInfo.format.ps1xml

You only need a single instance of an object. You can pipe multiple objects to New-PSFormatXML but subsequent ones will be ignored. The command will generate an xml file like this:

<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 02/18/2019 12:19:49
by BOVINE320\Jeff
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 02/18/2019 12:19:49 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>mySysInfo</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Computername</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>OS</Label>
            <Width>27</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Version</Label>
            <Width>13</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>System</Label>
            <Width>20</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Services</Label>
            <Width>11</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Processes</Label>
            <Width>12</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <!--
            By default the entries use property names, but you can replace them with scriptblocks.
            <Scriptblock>$_.foo /1mb -as [int]</Scriptblock>
-->
              <TableColumnItem>
                <PropertyName>Computername</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>OS</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Version</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>System</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Services</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Processes</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

From here I could edit the file as necessary. But since I'm happy displaying all the properties in a table I'll update PowerShell with this information.

Update-FormatData -AppendPath .\mySysInfo.format.ps1xml

Now when I run my function, I get a table display by default.

Default table view

Adding More Views

I can also create additional views.

Get-SysInfo | New-PSFormatXML -Path .\mySysInfo.format.ps1xml -Properties "Computername","OS","Version" -ViewName OS -Append

Again, I modify the view, even adding custom properties. Here's my revised format file.

<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 02/18/2019 12:30:50
by BOVINE320\Jeff
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 02/18/2019 12:30:50 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>mySysInfo</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Computername</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>OS</Label>
            <Width>27</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Version</Label>
            <Width>13</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>System</Label>
            <Width>20</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Services</Label>
            <Width>11</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Processes</Label>
            <Width>12</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <!--
            By default the entries use property names, but you can replace them with scriptblocks.
            <Scriptblock>$_.foo /1mb -as [int]</Scriptblock>
-->
              <TableColumnItem>
                <PropertyName>Computername</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>OS</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Version</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>System</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Services</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Processes</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 02/18/2019 12:31:00 by BOVINE320\Jeff-->
      <Name>OS</Name>
      <ViewSelectedBy>
        <TypeName>mySysInfo</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
            <Label>Computername</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>OS</Label>
            <Width>40</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Version</Label>
            <Width>13</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <!--
            By default the entries use property names, but you can replace them with scriptblocks.
            <Scriptblock>$_.foo /1mb -as [int]</Scriptblock>
-->
              <TableColumnItem>
                <PropertyName>Computername</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <Scriptblock>$($_.OS -replace "^Microsoft","").trim()</Scriptblock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Version</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

In this view I'm using a scriptblock to define the OS property that strips off 'Microsoft' from the name.  To use I update the format data and pipe the command to Format-Table specifying the view name.

Using a custom table view

Make it Pretty

Now there is no reason not have pretty or at least meaningful output from your functions. If you are writing custom objects to the pipeline, as long as you define a typename, it should now be much easier to create the formatting directives. In your module manifest, specify your format.ps1xml files in the FormatsToProcess section.

I hope you find this a useful addition to your PowerShell toolbox. I know this is something I will be using all the time now in my work.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

2 thoughts on “PowerShell Format Files the Easy Way”

  1. Mike Shepard says:
    February 18, 2019 at 9:10 pm

    I really love this. I’ve played around with format.ps1xml files but it’s usually a matter of finding one that’s close and cloning it. This looks like a super step forward from that.

  2. Pingback: Formatting Powershell Outputs – Curated SQL

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d