The "Classes" feature in PowerShell v5+ can be super useful, but what happens if you need class-like functionality but are forced to use an older version of PowerShell? Never fear! We can get much of the functionality we see when using classes using the PSCustomObject type.

This post was inspired by a conversation I recently had with a friend. He had implemented a module that relied on classes only to find out that he needed to support some systems running PowerShell 4. With an upgrade being out of the question, I came up with an example that could get him most of what he was looking for.

If you haven't used PS classes before, I suggest you read up on them: Microsoft Docs: About Classes. Classes are great when you are working on a project where a compiled language might be overkill but you need tighter control over data types, or need to pass complex objects around between functions.

PSCustomObject

PSCustomObject has been around since PowerShell v3.0. PSCustomObject is similar to a hashtable in that it can store named properties, but it behaves a bit differently when displaying the data (much nicer formatting). The killer feature though, is the ability to not only add properties but also methods (just like classes!) to your object.

Lets say we wanted a class the would expect a number when the object was created, and then allowed you to do some simple math on the number via class methods. I know this seems like a silly use case, but it very clearly shows how you can use custom objects as if they were classes.

If you were developing this in PowerShell v5+, you might use a class:

class MyClass {
    [int]$SomeNum

    MyClass([int]$SomeNum) {
        $this.SomeNum = $SomeNum
    }

    [int] TimesFive() {
        return $this.SomeNum * 5
    }

    [int] TimesNum([int]$Num) {
        return $this.SomeNum * $Num
    }
}

$MyClassInstance = [MyClass]::New(5)
$MyClassInstance.TimesFive()

This code creates a new class called MyClass, then we instantiate a new instance of the class with $SomeNum defined as 5.

Now, let's do the same thing using PSCustomObject and Add-Member:

function New-MyFakeClass {
    param(
        $SomeNum
    )

    $MyFakeClass = [PSCustomObject]@{}

    [scriptblock]$TimesFive = {
        <#
        Returns "SomeNum" multiplied by five
        #>
        $this.SomeNumber * 5
    }

    [scriptblock]$TimesNum = {
        <#
        Returns "SomeNum" multiplied by an arbitrary integer
        #>
        param(
            [int]$Num
        )

        $this.SomeNumber * $Num
    }

    $MyFakeClass | Add-Member -MemberType NoteProperty -Name SomeNumber -Value $SomeNum
    $MyFakeClass | Add-Member -MemberType ScriptMethod -Name TimesFive -Value $TimesFive
    $MyFakeClass | Add-Member -MemberType ScriptMethod -Name TimesNum -Value $TimesNum

    $MyFakeClass
}

$MyFakeClassInstance = New-MyFakeClass -SomeNum 5
$MyFakeClassInstance.TimesFive()

This is a bit more code, so lets break it down a little.

First we create a "factory" function New-MyFakeClass (a function which creates new objects):

function New-MyFakeClass {
    param(
        $SomeNum
    )

Since we aren't using classes, we have to create a custom function to take the place of the class constructor. In a class, the constructor is responsible for initializing a new class object (setting initial values for properties, etc.).

Within this function we create an empty object called $MyFakeClass. Next we define two script blocks. These script blocks take the place of the method definitions in the class-based example above. Once we have those defined we can start adding members to the object:

$MyFakeClass | Add-Member -MemberType NoteProperty -Name SomeNumber -Value $SomeNum
$MyFakeClass | Add-Member -MemberType ScriptMethod -Name TimesFive -Value $TimesFive
$MyFakeClass | Add-Member -MemberType ScriptMethod -Name TimesNum -Value $TimesNum

This adds a property named SomeNumber of type NoteProperty. NoteProperty members act just as a class property would, you can easily access them via: $MyFakeClassInstance.SomeNumber. After that we now add our two script blocks, but this time we use a type of ScriptMethod. The interesting thing about ScriptMethod is that the script blocks get access to the $this variable just like in a more formal class object. This gives the script methods access to the internal properties of the class instance, in this case the SomeNumber property.

Finally we can see how we use this new function to create new objects, and how we can access the ScriptMethod just like a standard class method:

$MyFakeClassInstance = New-MyFakeClass -SomeNum 5
$MyFakeClassInstance.TimesFive()

Thoughts

Another friend of mine told me that when you start using classes in PowerShell, it's time to look at a compiled language. In some cases I agree, but the maintainability of PowerShell when your team is NOT made up of software developers just can't be beat. I highly suggest reading about both Class and PSCustomObject. Even on PowerShell v5+, BOTH can be used to great effect.