Returning Rich Objects from Functions (Part 2)

by Jul 10, 2018

Whenever a function returns objects with more than four properties, PowerShell formats the output as list, else as table. Before you learn a new trick to influence this behavior, check for yourself. The function below returns an object with 6 properties:

function Get-TestData 
{
  # if a function is to return more than one information kind,
  # wrap it in a custom object

  [PSCustomObject]@{
      # wrap anything you'd like to return
      ID = 1
      Random = Get-Random
      Date = Get-Date
      Text = 'Hello'
      BIOS = Get-WmiObject -Class Win32_BIOS
      User = $env:username
    }
}

The result turns into a list:

 
PS> Get-TestData


ID     : 1
Random : 147704985
Date   : 25.05.2018 13:09:26
Text   : Hello
BIOS   : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name="1.6.1",SoftwareElementID="1.6.1",SoftwareElementState=3,TargetOperatingSys
         tem=0,Version="DELL   - 1072009"
User   : tobwe
 

When you remove properties and limit them to 4 or less, PowerShell produces a table:

 
PS> Get-TestData

ID    Random Text  User 
--    ------ ----  ---- 
 1 567248729 Hello tobwe
 

Typically, tabular design is easier to read, especially when there are multiple data sets. While you get tabular design by default with 4 or less properties, you might not always want to limit your return values to just 4 properties. So why not do it like cmdlets do?

Cmdlets by default show only a fraction of properties:

 
PS> Get-Service | Select-Object -First 1

Status   Name               DisplayName                           
------   ----               -----------                           
Running  AdobeARMservice    Adobe Acrobat Update Service
 

You get the full property list only when you use Select-Object and ask for all properties explicitly:

 
PS> Get-Service | Select-Object -First 1 -Property *


Name                : AdobeARMservice
RequiredServices    : {}
CanPauseAndContinue : False
CanShutdown         : False
CanStop             : True
DisplayName         : Adobe Acrobat Update Service
DependentServices   : {}
MachineName         : .
ServiceName         : AdobeARMservice
ServicesDependedOn  : {}
ServiceHandle       : 
Status              : Running
ServiceType         : Win32OwnProcess
StartType           : Automatic
Site                : 
Container
 

Apparently, there are first- and second-class citizens. In your own functions, you define the first-class citizen like so:

function Get-TestData 
{
  # define the first-class citizen
  [string[]]$visible = 'ID','Date','User'
  $info = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',$visible)


  [PSCustomObject]@{
      # wrap anything you'd like to return
      ID = 1
      Random = Get-Random
      Date = Get-Date
      Text = 'Hello'
      BIOS = Get-WmiObject -Class Win32_BIOS
      User = $env:username
    } |
    # add the first-class citizen info to your object
    Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $info -PassThru
  
}

Now, your function behaves exactly like cmdlets, and as long as you don’t define more than 4 first-class citizens, you get tabular design by default:

 
PS> Get-TestData

ID Date                User 
-- ----                ---- 
 1 25.05.2018 13:15:15 tobwe



PS> Get-TestData | Select-Object -Property *


ID     : 1
Random : 1298877814
Date   : 25.05.2018 13:15:22
Text   : Hello
BIOS   : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name="1.6.1",SoftwareElementID="1.6.1",SoftwareElementState=3,TargetOperatingSys
         tem=0,Version="DELL   - 1072009"
User   : tobwe
 

Twitter This Tip! ReTweet this Tip!