Enumerated Types in Windows PowerShell 5.0

January 5th, 2015 by June Blender
Last updated on April 8th, 2016

 

[Update: The original post referred to “enums” as “enumerators” when, in fact, they are “enumerated types.” By contrast, enumerators allow you to process items one at a time. Many thanks to Windows PowerShell MVP Dave Wyatt (@msh_Dave) for the correction.]

Enums have always been a part of Windows PowerShell, because they’re an important part of the .NET Framework. But they’re about to become even more popular, because changes in Windows PowerShell 5.0 make them very easy to create.

———–

Enumerated types (“enums”) define a set of values, knows as “members” of the enum. You use enums to create a set of valid values and prohibit all other values. In Windows PowerShell, you can use enumerated types just as you use .NET classes. For example, the value of the ExecutionPolicy parameter of the Set-ExecutionPolicy cmdlet has a type of ExecutionPolicy and its Scope parameter has a type of ExecutionPolicyScope.

Set-ExecutionPolicy [-ExecutionPolicy] <ExecutionPolicy> [[-Scope] <ExecutionPolicyScope>] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]

Both ExecutionPolicy and ExecutionPolicyScope are enums. The enum members are listed and defined on the MSDN page for each enum.

image

If you try to use a value that is not a member of the enum, you get a very helpful error. The error explains that the value is limited to the enum and lists the valid values. So, a quick way to find the members of an enum is to try a value that is clearly not valid, like good old “foo”.

[ADMIN]: PS C:\ps-test> Set-ExecutionPolicy -ExecutionPolicy foo
Set-ExecutionPolicy : Cannot bind parameter 'ExecutionPolicy'. Cannot convert value "foo" 
to type "Microsoft.PowerShell.ExecutionPolicy". Error: "Unable to match the identifier 
name foo to a valid enumerator name. Specify one of the following enumerator names and try 
again: Unrestricted, RemoteSigned, AllSigned, Restricted, Default, Bypass, Undefined"
At line:1 char:38
+ Set-ExecutionPolicy -ExecutionPolicy foo
+                                      ~~~
+ CategoryInfo          : InvalidArgument: (:) [Set-ExecutionPolicy], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand

 

It’s easy to use enums. This excellent article from the TechNet Library explains it well. And, Windows PowerShell has let you define your own enums since 1.0, but it was a bit tricky. Not any more!

 

Create an enum

In Windows PowerShell 5.0, the enum keyword makes it much easier. Here’s a WineSweetness enum that I created for my Wine class.

enum WineSweetness
{
    VeryDry
    Dry
    Moderate
    Sweet
    VerySweet
}

 

I use it to limit parameter values, much like ValidateSet, but unlike ValidateSet, once it’s defined in my session, script, or module, I can use it repeatedly without redefining it. In this example, I use it to limit the values of the Sweetness property of instances of my Wine class.

class Wine
{
    #Properties
    [String]$Name
    [WineSweetness]$Sweetness
    ...
}

I can also use it functions, such as my Get-Wine function, without redefining it.

function Get-Wine
{
    Param
    (
    [parameter(Mandatory = $false)]
    [WineSweetness]
    $SweetnessValue,
    ...
}

 

Enum syntax

The enum syntax is so easy. Just note that the list is NOT comma-separated. If you prefer to list the values on a single line, use semi-colons.

enum 
{
    Value1
    [Value2...]
}

-or-

enum 
{
    Value1; [Value2...;]
}

 

The value names cannot include spaces or special characters, although an underscore (_) is permitted. Enclosing a names with spaces in quotation marks or curly braces doesn’t help. If you include a space, Windows PowerShell thinks it’s an expression, which is permitted.

enum WineSweetness
{
    VeryDry
    Dry
    Moderate
    Sweet
    Very Sweet   # <--- No spaces
}
Very : The term 'Very' is not recognized as the name of a cmdlet, function, script file, 
or operable program. Check the spelling of the name, or if a path was included, verify 
that the path is correct and try again.
At line:1 char:5
+     Very Sweet
+     ~~~~
+ CategoryInfo          : ObjectNotFound: (Very:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

 

The values cannot be numbers and the first character of a value name cannot be a number. The ‘ missing closing “}” ‘ error is not very helpful, so watch out for this one.

PS C:\> enum WineSweetness
{
    VeryDry
    Dry
    3            # <------ No numbers
    Sweet
    VerySweet
}
At line:4 char:8
+     Dry
+        ~
Missing closing '}' in statement block or type definition.
At line:8 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
+ CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndCurlyBrace

 

Enums have integer values

Every enum value has a corresponding integer value. When you create an enum in Windows PowerShell (by using the Add-Type cmdlet or the enum keyword), the parser assigns a default integer value to each enum value. These default integer values begin with zero and increase by 1. The values are assigned in the order they are listed in the enum declaration.

For example, when I created my WineSweetness enum, Windows PowerShell assigned the values 0 – 4 to the enum values.

enum WineSweetness
{
    VeryDry
    Dry
    Moderate
    Sweet
    VerySweet
}

 

To find the values in any enum, call the GetValues static method of the System.Enum type:

PS C:\> [System.Enum]::GetValues([WineSweetness])

VeryDry
Dry
Moderate
Sweet
VerySweet

 

To find the underlying integer value of each enum value, cast the enum value to an integer (thanks to Keith Babinec for this trick).

PS C:\> [System.Enum]::GetValues([WineSweetness]) | foreach { [int]$_ }

0
1
2
3
4

 

Let’s put them together. This command creates a custom object (I love PSCustomObject!) with the names and integer value of each enum value.

PS C:\> [System.Enum]::GetValues([WineSweetness]) | 
foreach { [PSCustomObject]@{ValueName = $_; IntValue = [int]$_  }   }

ValueName              IntValue
---------              --------
VeryDry                       0
Dry                           1
Moderate                      2
Sweet                         3
VerySweet                     4

 

You can also assign integer values, and expressions that return an integer value, to your enum values. The values that you assign override the default zero-based values. All enum values are constants, so you can’t change them after you assign them.

PS C:\> enum WineSweetness
{
    Sweet = 4
    VeryDry = 2 * 4 + 2
    Moderate = 6
    VerySweet = 2
    Dry = 8
}

[System.Enum]::GetValues([WineSweetness]) | 
foreach {[PSCustomObject]@{ValueName = $_; IntValue = [int]$_} }

ValueName                  IntValue
---------                  --------
VerySweet                         2
Sweet                             4
Moderate                          6
Dry                               8
VeryDry                          10

 

Using an enum

When you use an enum, you can specify either a value name or its integer value.

enum WineSweetness
{
    VeryDry = 1
    Dry = 2
    Moderate = 3
    Sweet = 4
    VerySweet = 5
}

 

I’ll create an instance of my Wine class and set its Sweetness property, which has a type of WineSweetness, to VeryDry (my personal favorite).

PS C:\>$PSWine = [Wine]::new("PSCabernet")
PS C:\>$PSWine.Sweetness = VeryDry

I can use the dot method to get the string representation of the Sweetness property value:

PS C:\> $PSWine.Sweetness
VeryDry

Or, use the Value__ (that’s 2 underscores) property of all enum values to get its integer value.

PS C:\> $PSWine.Sweetness.value__
1

If I try to change the value of the Sweetness property to a value that’s not in the enum, I get that awesome error that lists the valid values.

PS C:\ps-test> $PSWine.Sweetness = "TooSweet"
Exception setting "Sweetness": "Cannot convert value "TooSweet" to type "WineSweetness". 
Error: "Unable to match the identifier name TooSweet to a valid enumerator name. 
Specify one of the following enumerator names and try again:
VeryDry, Dry, Moderate, Sweet, VerySweet""
At line:1 char:1
+ $PSWine.Sweetness = "TooSweet"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting

Just so I use a valid value, I can assign either a string or a integer value.

PS C:\> $PSWine.Sweetness = Dry
PS C:\> $PSWine.Sweetness
Dry

PS C:\> $PSWine.Sweetness = 3
PS C:\> $PSWine.Sweetness
Moderate

When you sort objects by an enumerated property value, the Sort-Object cmdlet sorts by integer value. For example, I have some wines in a $myWines variable.

PS C:\ps-test> $myWines

Name        : Great Duck
Winery      : Escalante Winery
Year        : 2003
isSparkling : True
Color       : White
Sweetness   : VerySweet
Description : Rich and magnificent
Rating      : {98, 97}

 

Name        : PSCabernet
Winery      : Chateau Snover
Year        : 2006
isSparkling : False
Color       : Red
Sweetness   : VeryDry
Description : Pithy
Rating      : {96}

 

Name        : Shiraz
Winery      : SAPIEN Vineyards
Year        : 1990
isSparkling : False
Color       : Red
Sweetness   : Dry
Description : Bold and innovative
Rating      : {100}

 

When I sort them by the value of the Sweetness property, I get them in ascending integer value order. I used a calculated property to add the integer value to the table.

PS C:\ps-test> $mywines |
Sort-Object -Property Sweetness |
Format-Table -Property Name, `
Sweetness, `
@{Label="IntSweet"; Expression={$_.Sweetness.Value__}} -AutoSize

Name       Sweetness IntSweet
----       --------- --------
PSWine       VeryDry        1
Shiraz           Dry        2
Great Duck VerySweet        5

 

The introduction of the enum keyword has really made enumerated types available to everyone. Yet another tool for your Windows PowerShell toolbox.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 

 
[Google+]   [Facebook]   [LinkedIn]   [StumbleUpon]   [Digg]   [Reddit]   [Google Bookmark]  

Tags: ,