Object Polishing
In this issue:
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.

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.

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.