Creating Enums In PowerShell

Creating Enums In PowerShell

In a previous blog post I talked about working with enums in PowerShell and how useful they are. Would they not be even more useful if we could create and use our own enums? Yes, yes they would. And if you read below you’ll find out how.

You’ve been able to create enums in PowerShell since v1.0. But since the introduction of PowerShell version 5 there are now two ways:

  1. the ‘version 5 way’, and
  2. the ‘before version 5 way’

Although PowerShell 5 has been around for over a year now, I still frequently come across clients who have not upgraded their PowerShell version beyond what came out of the box with the Operating System. So, for many people, it’s important to show you the way it was done pre-version 5 as it will still be relevant.

Creating Enums In PowerShell 1, 2, 3 and 4

We use the Add-Type cmdlet to define an enum:

PS> Add-Type -TypeDefinition @"
        public enum RebelBase
        {
            D_Qar,
            Dantooine,
            Hoth,
            Yavin_4
        }
"@

PS> $base = [RebelBase]::Hoth
PS> $base
Hoth

Creating Enums in PowerShell 5

PS> enum RebelBase { 
        D_Qar;
        Dantooine;
        Hoth;
        Yavin_4
}

PS> $base = [RebelBase]::Hoth
PS> $base
Hoth

Enum Syntax Caveats

  1. When creating an enum on multiple lines the ‘PowerShell 5 way’, like above, the semicolons are not strictly required but it’s good practice to use them as they are needed when creating the enum all on one line: enum RebelBase { D_Qar; Dantooine; Hoth; Yavin_4 };
  2. The enum members cannot include spaces or special characters (except an underscore) - don’t be smart and put it in quotes to get around this as it won’t work;
  3. The enum members cannot be numbers;

Assigning Numbers To Enum Members

As I said in the previous blog post, each enum member is assigned a number dependent on the order in which they were added, starting at 0, which we can either access by casting the enum to an [int] or by using the value__ property (note the double underscore).

PS> enum RebelBase { 
        D_Qar;
        Dantooine;
        Hoth;
        Yavin_4
}

PS> [RebelBase]::Hoth.value__
2

PS> [int][RebelBase]::Hoth
2

Hoth is added third and is therefore assigned the value of 2 (remember numbering starts from 0). But we can assign our own values to enum members:

PS> enum RebelBase { 
        D_Qar = 8;
        Dantooine;
        Hoth = 27;
        Yavin_4
}

PS> [enum]::GetNames([RebelBase]) | % { "$([RebelBase]::$_.value__) - $_" }
8 - D_Qar
9 - Dantooine
27 - Hoth
28 - Yavin_4
  1. We assigned the number 8 to D_Qar and the numbering now starts at that;
  2. So the next member, Dantooine continues the numbering and is assigned 9;
  3. The next member, Hoth, is assigned the number 27 so the numbering now starts at that;
  4. Yavin_4 continues the numbering and is assigned 28;

When explicitly specifying numbers for your enum members, it restarts the counter and applies to each subsequent member, incrementing by 1 as it goes.

Creating Enums From Enums

If you want to create a new enum using values from other enums you can do so. The members in the new enum will have a sum of all the other enum members that have been added to it.

PS> enum Component { Laser = 15; Bomb; Torpedo; Shields = 25; 
        Hyperdrive }

PS> enum Ship {
        Millenium_Falcon = [Component]::Laser + 
            [Component]::Laser + [Component]::HyperDrive + 
            [Component]::Shields;
        TIE_Fighter = [Component]::Laser + [Component]::Shields;
        X_Wing = [Component]::Laser + [Component]::Laser + 
            [Component]::Torpedo + [Component]::Shields +
            [Component]::Hyperdrive 
    }

# this will be 81 = Laser is 15 (x2) + Hyperdrive is 26  + 
# Shields is 25
PS> [Ship]::Millenium_Falcon.value__
81

# this will be 40 = Laser is 15 + Shields is 25
PS> [Ship]::TIE_Fighter.value__
40

# this will be 98 = Laser is 15 (x2) + Torpedo is 17 + 
# Shields is 25 + Hyperdrive is 26
PS> [Ship]::X_Wing.value__
98

Using Enums With Functions

If we create a function and want to restrict a parameter to a set of values we generally use [ValidateSet()].

function Set-DeathStarTarget {
    Param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet( "D_Qar", "Dantooine", "Hoth", "Yavin_4")]
        [string]$Planet
    )

    # Deploy the planet blower-upper gun!
}

The values of $Planet is restricted to those in [ValidateSet()]. If we later add another function that requires the same set of values.

function Invoke-PlanetInvasion {
    Param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet( "D_Qar", "Dantooine", "Hoth", "Yavin_4")]
        [string]$Planet
    )

    # Start the invasion of the rebel scum planets!
}

We now have two functions using the same validated set of parameters - "D_Qar", "Dantooine", "Hoth", "Yavin_4". What if those rebel scum build another base? We now have to update our function in two separate places to allow the operator to have that new choice. What if we have 4, 5, 6 or more functions that use the same value set? We have even more places to update each time we add a new planet.

The solution is simple and as you’re reading an article on enums, you’d probably guess that the answer is actually an enum. And you’d be right!

If we used an enum at the start of our script we could use that to validate our values saving us time and improving the readability of our code.

enum RebelBase { D_Qar; Dantooine; Hoth; Yavin_4; Atollon }

function Set-DeathStarTarget {
    Param (
        [Parameter(Mandatory)]
        [RebelBase]$Planet
    )

    # Deploy the planet blower-upper gun!
}

function Invoke-PlanetInvasion {
    Param (
        [Parameter(Mandatory)]
        [RebelBase]$Planet
    )

    # Start the invasion of the rebel scum planets!
}

Yes you can do away with validating the parameter being empty or null or against a set of values. All using an enum.

Using Enums With Switch

When you use an enum in a switch you can use the enum member name to match against as it is converted to a string:

$planet = [RebelBase]::Dantooine

switch ($planet)
{
    "D_Qar" {
        $base = "D'Qar (it's covered in jungle)"
        continue
    }

    "Dantooine" {
        $base = "Dantooine (rural forested planet)"
        continue
    }

    "Hoth" {
        $base = "Hoth (it's cold)"
        continue
    }

    "Yavin_4" {
        $base = "Yavin 4 (it's a moon)"
        continue
    }

    "Atollon" {
        $base = "Atollon (it's a desert planet)"
        continue
    }
}

"The rebel scum have a base on $base"

And that’s your lot on enums! Enjoy using them, enjoy making your code easier to read and enjoy restricting your data to pre-defined values!