Classes in PowerShell and Milking Your Variables

Posted by:

|

On:

|

Classes are a common term in object-oriented programming, which you’d see normally in a language such as C# or Python. By using classes of data, you can create your own data types with properties, methods, inheritance, and more. PowerShell supports classes and this can be used to make your scripts cleaner and easier to read.

When you declare classes, you’re creating a blueprint that can make instances of objects at runtime. For instance, if you declare the class Device and initialize the variable $thinkpad using it, $thinkpad becomes a logical instance of a Device type. If you initialized the $macbook variable using the Device, it would have the same blueprint as $thinkpad, but could hold different data.

Class Definition in PowerShell

First, we’ll define the Device class.

PowerShell
class Device {
    [string]$Brand # No commas!
    # If every device has to have a brand, we could use:
    # [ValidateNotNullOrEmpty()] [string]$Brand
    [string]$Color
}

Now, we can initialize a variable.

PowerShell
$thinkpad = [Device]::new()
$thinkpad.Brand = "Lenovo"
$thinkpad.Color = "Black"
$thinkpad

Notice that when calling the variable, we get a table with it’s data. If we do the same using a different variable, the data is separate but uses the same method.

PowerShell
$macbook = [Device]::new()
$macbook.Brand = "Apple"
$macbook.Color = "Silver"
$macbook

You can also make Constructors that require input to initialize a variable using a class. This allows you to add parameters during variable initialization to avoid method-based assignment.

PowerShell
class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$device = [Device]::new(
    "Fabrikam, Inc.",
    "Fbk5040",
    "5072641000"
)

$device

It’s possible to initialize classes using other classes as well. In this example from Microsoft, there is a class Rack which can hold a set number of Devices, and the Fabrikam device is added to the third slot. This shows you can use methods to put a Device on the Rack and keep track of everything.

PowerShell
class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    [string]ToString(){
      return ('{0}|{1}|{2}' -f $this.Brand, $this.Model, $this.VendorSku)
    }
}

class Rack {
    [int]$Slots = 8
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    [void] AddDevice([Device]$dev, [int]$slot){
      ## Add argument validation logic here
      $this.Devices[$slot] = $dev
    }

    [void]RemoveDevice([int]$slot){
      ## Add argument validation logic here
      $this.Devices[$slot] = $null
    }

    [int[]] GetAvailableSlots(){
      [int]$i = 0
      return @($this.Devices.foreach{ if($_ -eq $null){$i}; $i++})
    }
}

$rack = [Rack]::new()

$device = [Device]::new()
$device.Brand = "Fabrikam, Inc."
$device.Model = "Fbk5040"
$device.VendorSku = "5072641000"

$rack.AddDevice($device, 2)

$rack
$rack.GetAvailableSlots()

If there’s a value we don’t want to change, we can set it as hidden. This will still be visible if checked but helps prevent accidental changes.

PowerShell
class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    [int] hidden $Slots = 8
    [string]$Brand
    [string]$Model
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)
    }
}

[Rack]$r1 = [Rack]::new("Fabrikam, Inc.", "Fbk5040", 16)

$r1
$r1.Devices.Length
$r1.Slots

If you want to have all instances of a class rely on the same data, you can use a static attribute.

PowerShell
class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    hidden [int] $Slots = 8
    static [Rack[]]$InstalledRacks = @()
    [string]$Brand
    [string]$Model
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [string]$id, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.AssetId = $id
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)

        ## add rack to installed racks
        [Rack]::InstalledRacks += $this
    }

    static [void]PowerOffRacks(){
        foreach ($rack in [Rack]::InstalledRacks) {
            Write-Warning ("Turning off rack: " + ($rack.AssetId))
        }
    }
}

# Test the racks
[Rack]::InstalledRacks.Length

[Rack]::PowerOffRacks()

(1..10) | ForEach-Object {
    [Rack]::new("Adatum Corporation", "Standard-16",
        $_.ToString("Std0000"), 16)
} > $null

[Rack]::InstalledRacks.Length

[Rack]::InstalledRacks[3]

[Rack]::PowerOffRacks()

This is just the surface of PowerShell classes. We haven’t touched on inheritance, pulling classes from PowerShell modules, and more. For the scripting enthusiast, this allows for some tight code and makes life much easier.