Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
November 11, 2025

Object Polishing

In this issue:

  • Custom Properties
  • Add-Member
  • Update-TypeData
    • Default Display Properties
    • Default Key Property
  • Summary

We all know that using objects in PowerShell is what makes it such a compelling and useful management and automation tool. I don't have to try and parse text output. Working with objects, at least for me, is much more intuitive and productive. Another great feature of PowerShell is that you don't need to be a .NET developer to use it. I've already shown you how easy it is to create custom objects out of thin air. This is something we do often when writing PowerShell functions.

The thing that will set you apart is when you take the next level and consider what you do to add value to those objects. How can you make the output of your function easier to use? How can you make it more informative? How can you make it easier to consume in a pipelined expression?

I am not talking about formatting. That is a matter of how the object is displayed. I am talking about what the object contains. How can you make your objects more useful? How can you extend them?

Custom Properties

Let's go back to the beginning. Here's a typical PowerShell expression.

PS C:\> Get-CimInstance -ClassName win32_process -Filter "ProcessID=$pid"

ProcessId Name     HandleCount WorkingSetSize VirtualSize
--------- ----     ----------- -------------- -----------
14456     pwsh.exe 917         136843264      2481052332032

You can pipe this to Get-Member to see what properties are available on this object.

PS C:\> Get-CimInstance -ClassName win32_process -Filter "ProcessID=$pid" | Select-Object ProcessID, Name, CreationDate, Path, CommandLine,HandleCount,ThreadCount

ProcessID    : 14456
Name         : pwsh.exe
CreationDate : 11/5/2025 1:03:59 PM
Path         : C:\Program Files\PowerShell\7\pwsh.exe
CommandLine  : pwsh.exe -NoLogo -noProfile -NoExit -command "&{. c:\scripts\miniprofile7.ps1}"
HandleCount  : 852
ThreadCount  : 24

With Select-Object, you can create custom properties using calculated properties. These are defined with a specific hashtable syntax. The hashtable must contain two keys: Name and Expression. The value of the Name key is the name of the new property. The value of the Expression key is a script block that defines how to calculate the value of the property.

$p = Get-CimInstance -ClassName win32_process -Filter "ProcessID=$pid" |
Select-Object ProcessID, Name, CreationDate, Path, CommandLine,
@{Name = 'Handles'; Expression = { $_.HandleCount } },
@{Name = 'Threads'; Expression = { $_.ThreadCount } },
@{Name = 'Username'; Expression = {
    $r = Invoke-CimMethod -InputObject $_ -MethodName GetOwner
    '{0}\{1}' -f $r.Domain, $r.User
    }
},
@{Name = 'Computername'; Expression = { $_.CSName } }

Instead of using HandleCount and ThreadCount directly, I created new alias properties called Handles and Threads. These properties simply return the values of the original properties. But I also created a new property called Username. This property uses the GetOwner method of the Win32_Process class to retrieve the process owner. This property doesn't exist by default. I created it.

PS C:\> $p

ProcessID    : 14456
Name         : pwsh.exe
CreationDate : 11/5/2025 1:03:59 PM
Path         : C:\Program Files\PowerShell\7\pwsh.exe
CommandLine  : pwsh.exe -NoLogo -noProfile -NoExit -command "&{. c:\scripts\miniprofile7.ps1}"
Handles      : 946
Threads      : 23
Username     : PROSPERO\Jeff
Computername : PROSPERO

> This process is an art more than a science. You have to consider how the user, which might be you in six months, will want to use this object. What properties will be useful? What property names make more sense? How can you make this object easier to use in a pipeline?

Add-Member

One approach you can take is to use the Add-Member cmdlet. This cmdlet allows you to add properties and methods to an existing object. For example, I want to define an alias property called Started that is an alias for the existing CreationDate property.

$p | Add-Member -MemberType AliasProperty -Name Started -Value CreationDate -Force

You can create a ScriptProperty that uses a script block to calculate the value of the property. The Value parameter is a script block. Use $this to refer to the current object. In my example, I want to add a property called Runtime that calculates how long the process has been running.

$p | Add-Member -MemberType ScriptProperty -Name Runtime -Value { New-TimeSpan -Start $this.CreationDate -End (Get-Date) } -Force

You'll get a new value every time you access the property.

Or, you may want to add a static property that has a fixed value. This is done with a NoteProperty. In my example, I want to add a property called LastCheck that contains the date and time when I last checked the process.

$p | Add-Member -MemberType NoteProperty -Name LastCheck -Value (Get-Date) -force

Where this really gets fun is that you can easily add a method to your object without having to write complicated .NET code. In my example, I want to add a method called Refresh that will re-query the process information from the computer and update the object.

$p | Add-Member -MemberType ScriptMethod -Name Refresh -Value {
    $r = Get-CimInstance -ClassName win32_process -Filter "ProcessID=$($this.ProcessID)" -ComputerName $this.Computername
    $this.Handles = $r.HandleCount
    $this.Threads = $r.ThreadCount
    $this.LastCheck = Get-Date
    $this
} -Force

This method assumes your credential has permission to query the process information on a remote computer.

You can see these additions with Get-Member.

Added object members
figure 1

The custom objects has no defined formatting, so you get all the properties when you display it.

PS C:\> $p

ProcessID    : 14456
Name         : pwsh.exe
CreationDate : 11/5/2025 1:03:59 PM
Path         : C:\Program Files\PowerShell\7\pwsh.exe
CommandLine  : pwsh.exe -NoLogo -noProfile -NoExit -command "&{. c:\scripts\miniprofile7.ps1}"
Handles      : 1289
Threads      : 25
Username     : PROSPERO\Jeff
Computername : PROSPERO
Started      : 11/5/2025 1:03:59 PM
LastCheck    : 11/5/2025 1:35:58 PM
Runtime      : 00:38:34.9006460

When I invoke the Refresh method, the object updates its properties.

PS C:\> $p.refresh()

ProcessID    : 14456
Name         : pwsh.exe
CreationDate : 11/5/2025 1:03:59 PM
Path         : C:\Program Files\PowerShell\7\pwsh.exe
CommandLine  : pwsh.exe -NoLogo -noProfile -NoExit -command "&{. c:\scripts\miniprofile7.ps1}"
Handles      : 886
Threads      : 24
Username     : PROSPERO\Jeff
Computername : PROSPERO
Started      : 11/5/2025 1:03:59 PM
LastCheck    : 11/5/2025 1:43:36 PM
Runtime      : 00:39:37.3766762

There is one critical caveat with Add-Member. The changes you make are only applied to the specific object instance. If you create a new object of the same type, it will not have the custom properties and methods you added.

Notepad process
figure 2

I have the custom properties defined with Select-Object but not the Add-Member additions from the other process object. This is not to imply that Add-Member is not useful. It is very useful when you want to extend a specific object instance. Just be aware of this behavior.

Want to read the full issue?
GitHub Bluesky LinkedIn Mastodon
Powered by Buttondown, the easiest way to start and grow your newsletter.