Powershell

Custom output formatting with Powershell

Now that I have my custom type I can use cmdlets such as Format-Table or Format-List to format its output while at the console, but it would be nice if I did not have to remember to usem the every time. Powershell uses Format.ps1xml to define how types should be formatted when their output is sent to the console and this technique can come to our rescue.

If an output object exposes up to 4 properties, by default Powershell formats it as a table while objects with 5+ displayed properties are formatted as list for better readability; for example, in this first example I am selecting only four properties and Powershell prints data in a table format, while in the second example I am selecting five properties and Powershell presents then in a list:

λ 16 carlo@CARLOCXPS 21:36:08 carlo >_ dir | select Name, Exists, Mode, CreationTime

Name                                       Exists Mode   CreationTime
----                                       ------ ----   ------------
.atom                                        True d----- 4/18/2018 8:04:37 AM
.azure                                       True d----- 3/23/2018 7:48:42 PM
.azure-shell                                 True d----- 12/30/2018 1:40:08 PM
.config                                      True d----- 4/27/2018 8:35:53 PM
.dotnet                                      True d----- 3/15/2018 1:48:02 PM
.electron                                    True d----- 8/23/2018 5:17:56 PM
.hyper_plugins                               True d----- 6/4/2018 8:58:08 AM
.mume                                        True d----- 1/30/2019 8:26:59 PM
.nuget                                       True d----- 9/20/2018 9:55:27 AM
.omnisharp                                   True d----- 1/30/2019 7:10:26 AM
.rest-client                                 True d----- 8/3/2018 9:59:46 PM
.templateengine                              True d----- 4/6/2018 9:09:33 PM
.VirtualBox                                  True d----- 5/24/2018 9:36:28 PM
.vscode                                      True d----- 10/9/2018 11:24:33 AM
.vscode-exploration                          True d----- 2/1/2019 6:05:52 AM
.vscode-insiders                             True d----- 1/30/2019 5:48:11 AM




λ 18 carlo@CARLOCXPS 21:40:04 carlo >_ dir | select Name, Exists, Mode, CreationTime, LastWriteTime

Name          : .atom
Exists        : True
Mode          : d-----
CreationTime  : 4/18/2018 8:04:37 AM
LastWriteTime : 12/13/2018 7:16:48 PM

Name          : .azure
Exists        : True
Mode          : d-----
CreationTime  : 3/23/2018 7:48:42 PM
LastWriteTime : 1/1/2019 2:05:17 PM

Name          : .azure-shell
Exists        : True
Mode          : d-----
CreationTime  : 12/30/2018 1:40:08 PM
LastWriteTime : 12/30/2018 1:40:51 PM

Name          : .config
Exists        : True
Mode          : d-----
CreationTime  : 4/27/2018 8:35:53 PM
LastWriteTime : 4/27/2018 8:35:53 PM

This is not always true though, for example Get-Process returns a table with 6 columns:

λ 19 carlo@CARLOCXPS 21:40:26 carlo >_ Get-Process

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     64   107.13     143.16     297.45   16932   1 1Password
     10    17.79       9.43       0.00    5560   0 AdminService
     10     1.49       6.03       0.00    5496   0 AdobeUpdateService
     13     2.48       9.09       0.00    5528   0 AGMService
     16     4.20      13.95       0.00    5544   0 AGSService
     20     3.42      11.35      15.22   15356   1 AppleMobileDeviceProcess
     40    12.23      30.09       7.61   18688   1 ApplePhotoStreams
     31    21.56      34.46      10.39   19580   1 ApplicationFrameHost
      8     1.80       5.80       0.00   14972   0 AppVShNotify
     12     2.88       7.41       0.02   14984   1 AppVShNotify
     26     6.65      16.61       2.92   18708   1 APSDaemon
      9     1.37       5.71       0.00    5484   0 armsvc
     25    19.39       3.07       2.20   21136   1 Calculator

This is because Get-Process returns a type that happens to have a custom format defined; which type is that?

λ 20 carlo@CARLOCXPS 21:42:01 carlo >_ Get-Process | Get-Member

   TypeName: System.Diagnostics.Process
Name                       MemberType     Definition
----                       ----------     ----------
Handles                    AliasProperty  Handles = Handlecount
Name                       AliasProperty  Name = ProcessName
NPM                        AliasProperty  NPM = NonpagedSystemMemorySize64
PM                         AliasProperty  PM = PagedMemorySize64
SI                         AliasProperty  SI = SessionId
VM                         AliasProperty  VM = VirtualMemorySize64
WS                         AliasProperty  WS = WorkingSet64

System.Diagnostic.Process. If we check $PSHOME and search the *.format.ps1xml files we can find that C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml contains a definition for this .NET type:

<View>
  <Name>process</Name>
  <ViewSelectedBy>
    <TypeName>System.Diagnostics.Process</TypeName>
  </ViewSelectedBy>
  <TableControl>
    <TableHeaders>
      <TableColumnHeader>
        <Label>Handles</Label>
        <Width>7</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>NPM(K)</Label>
        <Width>7</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>PM(K)</Label>
        <Width>8</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>WS(K)</Label>
        <Width>10</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>VM(M)</Label>
        <Width>5</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>CPU(s)</Label>
        <Width>8</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Width>6</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Width>3</Width>
        <Alignment>right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader />
    </TableHeaders>
    <TableRowEntries>
      <TableRowEntry>
        <TableColumnItems>
          <TableColumnItem>
            <PropertyName>HandleCount</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>[long]($_.NPM / 1024)</ScriptBlock>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>[long]($_.PM / 1024)</ScriptBlock>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>[long]($_.WS / 1024)</ScriptBlock>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>[long]($_.VM / 1048576)</ScriptBlock>
          </TableColumnItem>
          <TableColumnItem>
            <ScriptBlock>
if ($_.CPU -ne $()) 
{ 
    $_.CPU.ToString("N") 
}
            </ScriptBlock>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>Id</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>SI</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>ProcessName</PropertyName>
          </TableColumnItem>
        </TableColumnItems>
      </TableRowEntry>
    </TableRowEntries>
  </TableControl>
</View>

This is a relatively complex View definition with custom column size and ScriptBlock for calculated fields I did not use in my AzSqlDatabaseSize example, but it still provides a bases to create my own. These files are signed to prevent tampering so we cannot modify them directly, but as for Types.ps1xml I created new .ps1xml file in my module’s folder (I usually use these with modules) and customize it as needed.

This is the simple view I created for my AzSqlDatabaseSize type:

<View>
  <Name>AzSqlDatabaseSize</Name>
  <ViewSelectedBy>
    <TypeName>AzSqlDatabaseSize</TypeName>
  </ViewSelectedBy>
  <TableControl>
    <TableHeaders>
      <TableColumnHeader>
        <Label>ServerName</Label>
        <Width>15</Width>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>DatabaseName</Label>
        <Width>15</Width>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>Sku</Label>
        <Width>5</Width>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>CurrentSizeGb</Label>
        <Width>15</Width>
        <Alignment>Right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>MaxSizeGb</Label>
        <Width>10</Width>
        <Alignment>Right</Alignment>
      </TableColumnHeader>
      <TableColumnHeader>
        <Label>%-Used</Label>
        <Width>8</Width>
        <Alignment>Right</Alignment>
      </TableColumnHeader>
    </TableHeaders>
    <TableRowEntries>
      <TableRowEntry>
        <TableColumnItems>
          <TableColumnItem>
            <PropertyName>ServerName</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>DatabaseName</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>Sku</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>CurrentSizeGb</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>MaxSizeGb</PropertyName>
          </TableColumnItem>
          <TableColumnItem>
            <PropertyName>%-Used</PropertyName>
          </TableColumnItem>
        </TableColumnItems>
      </TableRowEntry>
    </TableRowEntries>
  </TableControl>
</View>

It is a simpler View than the process one (I did not specify column width nor I used ScriptBlock for calculated fields) but it nicely format my output

λ 35 carlo@CARLOCXPS 14:13:56 carlo >_ Get-AzSqlDatabaseSize -ServerName MySqlServer -ResourceGroupName Default-SQL-EastUS2 -DatabaseName MySqlDatabase

ServerName      DatabaseName    Sku     CurrentSizeGb  MaxSizeGb   %-Used
----------      ------------    ---     -------------  ---------   ------
MySqlServer     MySqlDatabase   P1              50.63        500    10.13

As for custom types, custom format can be referenced in a module Data File and loaded automatically when importing the module into the session:

# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = 'Format.ps1xml'

When you were born, you cried and the world rejoiced. Live your life in such a manner that when you die the world cries and you rejoice. – Indian Proverb

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.