I Object
> My periodic reminder that if you need to manage your subscription, please use the links in the email footer. Don't forget that you can cancel a premium membership at anytime so I hope you'll consider it and discover what you've been missing.
In this issue:
I recently wrapped up teaching an advanced PowerShell scripting class. I spent a lot of time discussing the object-oriented nature of PowerShell. One of the key concepts I emphasized was the importance of understanding how to work with objects effectively in PowerShell. This includes how you define an object and how it might be consumed.
When you create a PowerShell function that writes something to the pipeline, you should pay attention to what is being sent as output. PowerShell commands rarely exist in isolation. When you craft a function, you have no way of knowing all the ways it might be used. Ideally, you will have some idea of how the user will run your function, even if the user is you. But you don't know what the user might need to do tomorrow or six months from now.
Much of what I want to cover today can be applied to native PowerShell commands and objects. But I'm going to discuss these concepts in the context of custom objects you create in your own functions. This will help you understand how to design your functions to be more flexible and reusable. I always tell my students that you want to design your commands to be as seamless and frictionless as possible. Anticipate the user and make it easy for them to get their work done.
Designing Objects
PowerShell by design writes objects to the pipeline. Even if your function is outputting a number or a string, those are still objects. More than likely, you will be creating richer objects with a multiple properties. The problem I encounter with many beginners is that they worry about what the object will look like to the user. That is a separate topic. It is very important that you separate the object from how it is displayed or formatted.
The object should be as rich as possible, with raw, unformatted property values. If your output includes a property value for the size of file, don't format it as KB or MB. The property value should be in bytes. I see this often. The scripter writes output already formatted as MB. But now they are stuck. If next week they need the value in GB, they either need to modify their function or write additional code to convert the value.
You don't want to make the user have to work any harder than they have to. Although, I'll admit this is a balancing act. Consider this code to get logon events from the Windows event log.
$Computername = 'Cadenza'
$Start = (Get-Date).AddHours(-2)
#Filter out System logons
$events = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4624
StartTime = $Start
} -ComputerName $ComputerName | where { $_.Properties[5].Value -ne 'System' }
Your function might write output like this:
$events | Select-Object TimeCreated,
@{Name = 'User'; Expression = { $_.properties[5].value } },
@{Name = 'Logon'; Expression = { $_.Properties[8].value } }
This is a good start.
PS C:\> $events | Select-Object TimeCreated,
@{Name = 'User'; Expression = { $_.properties[5].value } },
@{Name = 'Logon'; Expression = { $_.Properties[8].value } }
TimeCreated User Logon
----------- ---- -----
10/28/2025 2:52:42 PM roygbiv 2
10/28/2025 2:31:13 PM jeff 2
10/28/2025 2:31:13 PM jeff 2
10/28/2025 1:55:10 PM jeff 2
10/28/2025 1:55:10 PM jeff 2
...
For many PowerShell scripters, they may feel their work is done. While you could format the logon value into a more user-friendly string, in the same way you could provide default formatting of a disk size, an argument could be made it makes sense to translate the value in the code. Yes, there are always exceptions and you need to carefully consider the implications when you make them.
I can use a look-up hashtable to translate the logon type value into a more user-friendly string.
$logonHash = @{
2 = 'Interactive'
3 = 'Network'
4 = 'Batch'
5 = 'Service'
7 = 'Unlock'
8 = 'NetworkClearText'
9 = 'RunAs'
10 = 'Remote'
11 = 'OfflineCached'
}
$events | Select-Object TimeCreated,
@{Name = 'User'; Expression = { $_.properties[5].value } },
@{Name = 'Logon'; Expression = { $logonHash[$($_.Properties[8].value) -as [Int]] } }
Now the user has a more friendly output.
TimeCreated user Logon
----------- ---- -----
10/28/2025 2:52:42 PM roygbiv Interactive
10/28/2025 2:31:13 PM jeff Interactive
10/28/2025 2:31:13 PM jeff Interactive
10/28/2025 1:55:10 PM jeff Interactive
10/28/2025 1:55:10 PM jeff Interactive
10/28/2025 1:25:07 PM jeff Network
...
With this, the user could group on the Logon property and get a count of each logon type.
PS C:\> $events | Select-Object TimeCreated,
@{Name = 'User'; Expression = { $_.properties[5].value } },
@{Name = 'Logon'; Expression = { $logonHash[$($_.Properties[8].value) -as [Int]] } } | Group Logon -NoElement
Count Name
----- ----
6 Batch
5 Interactive
12 Network