Creating your own objects with PSCustomObject

PSCustomObject offers us an excellent way to create structured data in PowerShell. According to the documentation, [pscustomobject] is a type accelerator. Type accelerators “allow you to access specific .NET framework classes without having to explicitly type the entire class name”. The full class name for PSCustomObject is System.Management.Automation.PSObject, which means PSCustomObject is just an alias for that class. Digging a bit further, the PSObject Class “Wraps an object providing alternate views of the available members and ways to extend them. Members can be methods, properties, parameterized properties, etc.”.

The basic sintaxis to create an empty PSCustomObject is:

$exampleObject = New-Object -TypeName psobject

If we pipe $exampleObject to Get-Member, we will see that the type name is System.Management.Automation.PSCustomObject. To create this type of object we can also use:

$exampleObject = [PSCustomObject]@{}

With the latter we can add properties as it follows:

$exampleObject = [PSCustomObject]@{
    Name = "Bob"
    Age = 3
    City = "LA"
}

Adding or removing properties is straightforward. A lot of cmdlets in PS return an object, but sometimes we need to manipulate properties in different ways. Suppose you are checking if several services are running on a computer and you have the details about them: name, display name and status. If the service exists in the target computer, you should be able to retrieve its information with the Get-Service cmdlet. But if that’s not the case, Get-Service is going to return an error message indicating that the service does not exist. In that scenario, we can create a custom object to handle it. Let us see an example. First we have the information of two services that we need to check on the target computer:

$serviceOne = [PSCustomObject]@{
    Name = "serviceOne"
    DisplayName = "Service One Example"
}

$serviceTwo = [PSCustomObject]@{
    Name = "serviceTwo"
    DisplayName = "Service Two Example"
}

If we run Get-Service -Name $serviceOne.Name we will see an error (because is a made up service), but what if we want to see the details of those services that we have available in $serviceOne and $serviceTwo? To solve that we can use PSCustomObject. To do so, let us create a function with a try – catch and finally:

function Get-ServiceRunning {
    param (
        [string]$serviceRunning
    )
    try {
        $ErrorActionPreference = 'Stop'
        $serviceCheck = Get-Service -Name $EPAServiceName;
        $serviceCheck
    }
    catch {
        $customObject = [PSCustomObject]@{
            Status = 'Not running'
        }
        $customObject
    }
    finally {
        $ErrorActionPreference = 'Continue'
    }
}

There’s a little trick that we need to use to make the try and catch work. According to the documentation the try and catch “respond to or handle terminating errors in scripts”. The error that Get-Service returns when the service doesn’t exist in the computer is nonterminating, which means we have to tell PS that we want all errors to be terminating for the try and catch to work. To do so, we set $ErrorActionPreference to “Stop”. After that, the logic tells PS: if the services exist, return the result of Get-Service. Otherwise, create and return a custom object with the property status “Not running”. To avoid issues, the finally blocks sets $ErrorActionPreference to the initial value. Now we have to combine the information from $serviceOne, $serviceTwo and the Get-Service result in a single object. To keep things simple, we can create an array to join $serviceOne and $serviceTwo objects:

$servicesCollection = @()
$servicesCollection += $serviceOne
$servicesCollection += $serviceTwo

And now with a for we can join everything together:

for ($i = 0; $i -lt $servicesCollection.Count; $i++) {
    $serviceIN = $servicesCollection[$i]
    $serviceConstructor = New-Object -TypeName psobject

    $serviceConstructor | Add-Member NoteProperty "Name" $serviceIN.Name
    $serviceConstructor | Add-Member NoteProperty "DisplayName" $serviceIN.DisplayName
    $serviceConstructor | Add-Member NoteProperty "Status" (Get-ServiceRunning $serviceIN.Name).Status

    $serviceOutput += $serviceConstructor
}

Inside this loop we created a couple of variables:

  • $serviceIN to reference the element that is currently being manipulated in the loop.
  • $serviceConstructor as an object to take Name and DisplayName from $servicesCollection and Status from the object that Get-ServiceRunning function returns.

At the end we append the objects to the $serviceOutput array. Calling $serviceOutput shows a much nicer result with all the information in a single object, and we are not presented with an error in case the service does not exist:

Result of $serviceOutput

Here you have the full code:

$servicesCollection = @()
$serviceOutput = @()

$serviceOne = [PSCustomObject]@{
    Name = "serviceOne"
    DisplayName = "Service One Example"
}


$serviceTwo = [PSCustomObject]@{
    Name = "serviceTwo"
    DisplayName = "Service Two Example"
}

$servicesCollection += $serviceOne
$servicesCollection += $serviceTwo

function Get-ServiceRunning {
    param (
        [string]$serviceRunning
    )
    try {
        $ErrorActionPreference = 'Stop'
        $serviceCheck = Get-Service -Name $serviceRunning;
        $serviceCheck
    }
    catch {
        $customObject = [PSCustomObject]@{
            Status = 'Not running'
        }
        $customObject
    }
    finally {
        $ErrorActionPreference = 'Continue'
    }
}

for ($i = 0; $i -lt $servicesCollection.Count; $i++) {
    $serviceIN = $servicesCollection[$i]
    $serviceConstructor = New-Object -TypeName psobject

    $serviceConstructor | Add-Member NoteProperty "Name" $serviceIN.Name
    $serviceConstructor | Add-Member NoteProperty "DisplayName" $serviceIN.DisplayName
    $serviceConstructor | Add-Member NoteProperty "Status" (Get-ServiceRunning $serviceIN.Name).Status

    $serviceOutput += $serviceConstructor
}

$serviceOutput

| Theme: UPortfolio