PowerShell

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, “PowerShell: Creating an “empty” PSCustomObject” on X that got me curious. Do people create empty objects like Christian proposes? I want to offer an alternative to Christian's article, which uses OrderedDictionary and converts to PSCustomObject.

Converting OrderedDictionary to PSCustomObject

Here's a snippet code that shows how to create a custom object using OrderedDictionary and conversion process

$CustomObject = [ordered] @{}
$CustomObject['FirstName'] = 'John'
$CustomObject['LastName'] = 'Doe'
$CustomObject['UserName'] = 'John.Doe'
[pscustomobject] $CustomObject

You can also create custom objects dynamically using a similar approach to what Christian proposed.

$propertyDefinitions = @{
    Users   = @(
        "FirstName", "LastName", "UserName", "Title", "Department",
        "StreetAddress", "City", "State", "PostalCode", "Country",
        "PhoneNumber", "MobilePhone", "UsageLocation", "License"
    )
    Groups  = @(
        "DisplayName", "PrimarySMTP", "Description", "Owner", "Type"
    )
    JobRole = @(
        "DisplayName", "PrimarySMTP", "Description", "Type"
    )
}


$CustomObject2 = [ordered] @{}
foreach ($P in $propertyDefinitions.Users) {
    $CustomObject2[$P] = $null
}
[PSCustomObject] $CustomObject2

$CustomObject3 = [ordered] @{}
foreach ($P in $propertyDefinitions.Groups) {
    $CustomObject3[$P] = $null
}
[PSCustomObject] $CustomObject3

$CustomObject4 = [ordered] @{}
foreach ($P in $propertyDefinitions.JobRole) {
    $CustomObject4[$P] = $null
}
[PSCustomObject] $CustomObject4

OrderedDictionary additionally offers the ability to create an object with some data and then append data on the fly in your script. This means, for example, you could create a dictionary first at the beginning of the script, and during the whole run of the script, you could append data to it without ever pre-creating it in the first place, to finally convert it to a custom object.

$Properties = 'Title', 'Department', 'State'
$CustomObject1 = [ordered] @{
    Name = 'John'
    Age  = 30
    City = 'New York'
}
foreach ($P in $Properties) {
    $CustomObject1[$P] = $null
}
[pscustomobject] $CustomObject1

In a long script you could do

$CustomObject1 = [ordered] @{
    Name = 'John'
    Age  = 30
    City = 'New York'
}
<#
Do lots of code here
#>
$CustomObject1['Address'] = 'New Value'
<#
More code here
#>
$CustomObject1['State'] = 'New Value 2'
# Convert to PSCustomObject
[pscustomobject] $CustomObject1

Another benefit of using OrderedDictionary is that you can add to it inside other functions without ever having to destroy the object, overwrite it, or even knowing beforehand what it will look like.

function Add-Values {
    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Dictionary,
        [string] $Key,
        [object] $Value
    )
    $Dictionary[$Key] = $Value
}

$CustomObject1 = [ordered] @{
    Name = 'John'
    Age  = 30
    City = 'New York'
}
<#
Do lots of code here
#>
$CustomObject1['Address'] = 'New Value'
<#
More code here
#>
$CustomObject1['State'] = 'New Value 2'
Add-Values -Dictionary $CustomObject1 -Key 'ZipCode' -Value 'New Value 3'
<#
Even more code
#>
Add-Values -Dictionary $CustomObject1 -Key 'EmployeeID' -Value 'New Value 4'

# Convert to PSCustomObject
[pscustomobject] $CustomObject1

I hope this short blog post will help you decide between my and Christian solutions. Both solutions have their own strengths, and depending on who likes what, you may end up using one or the other. Christian solution is based on PowerShell 2.0 when it was the only way to create PSCustomObject.

# what Christian proposes
$customObject1 = [PSCustomObject]@{} # this line is actually not needed
$customObject1 | Select-Object -Property $PropertyNames

# Idential, with just one line of code, as Select-Object creates new object on the fly
'' | Select-Object -Property $PropertyNames

I was curious if there's any performance difference between those two solutions. Using a simple Measure-Command gives us the answer.

$PropertyNames = @(
    "FirstName", "LastName", "UserName", "Title", "Department",
    "StreetAddress", "City", "State", "PostalCode", "Country",
    "PhoneNumber", "MobilePhone", "UsageLocation", "License"
)

Measure-Command {
    for ($i = 0; $i -lt 100000; $i++) {
        $CustomObject1 = [ordered] @{}
        foreach ($P in $PropertyNames) {
            $CustomObject1[$P] = $null
        }
        $T = [pscustomobject] $CustomObject1
    }
}

Measure-Command {
    for ($i = 0; $i -lt 100000; $i++) {
       $T2 = '' | Select-Object -Property $PropertyNames
    }
}

In PowerShell 5.1, over 100k iterations, it takes about 1 second less for my approach than using Select-Object.

In PowerShell 7, the difference is about 500ms which is not noticeable.

To summarize, both solutions work; I believe OrderedDictionary conversion has more pros and, at least for me, is easier to read and understand how everything happens. Just a thing of note – you could also use Hashtable instead of OrderedDictionary, but then the order of properties is not guaranteed, which may or may not matter to you. In the end, the choice is always yours. Enjoy

This post was last modified on %s = human-readable time difference 12:29

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

1 month ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

2 months ago

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

7 months ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

11 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

1 year ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

1 year ago