I'VE GOT THE BYTE ON MY SIDE

57005 or alive

Using enums in Powershell

Jul 9, 2012 powershell

Enums are a very useful way to encode “options” in .NET programming. They offer a mechanism to represent a fixed set of known values, named in a developer-friendly way. Enums are used frequently in .NET programming, and in many places throughout the core system SDK.

Many folks don’t realize it, but Powershell provides very powerful built-in support for dealing with enums.

Let’s start examining this support by looking at the DayOfWeek enum. We begin by getting an instance of System.DateTime from the Get-Date cmdlet. DateTime objects have a DayOfWeek property which specifies, predictably, the day of the week.

PS> $now = Get-Date

PS> $now.DayOfWeek
Thursday

PS> $now | Get-Member DayOfWeek
   TypeName: System.DateTime
Name      MemberType Definition
----      ---------- ----------
DayOfWeek Property   System.DayOfWeek DayOfWeek {get;}

Possible enum values

We all know what the possible values are for the day of the week, but what if we didn’t? How can we obtain the list of possible values for the DayOfWeek enum type?

Lucky for us, there is a static method provided by the System.Enum class itself which will show us all possibilities. We just need to pass the enum type we are interested in.

PS> [Enum]::GetNames( [System.DayOfWeek] )
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

Creating enum instances

How does one go about creating an instance of an enum type in Powershell? There are a few ways to do it.

The most direct way is to use the same :: syntax used when accessing static .NET members:

PS> $enumVal = [System.DayOfWeek]::Monday
PS> $enumVal = [System.DayOfWeek]::Sunday

Another option is to cast a string containing a valid enum name into the enum type:

PS> $enumVal = [System.DayOfWeek] 'Sunday'

If you are passing an enum value as a parameter into a .NET method or a cmdlet, you rarely need to do an explicit cast or specify the whole type name. The Powershell runtime is smart enough to convert your string into the proper enum type, assuming the string contains a valid enum name.

# consider the method File.Open, it takes a FileMode enum parameter
PS> [Io.File]::Open

OverloadDefinitions
-------------------
static System.IO.FileStream Open(string path, System.IO.FileMode mode)
static System.IO.FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access)
static System.IO.FileStream Open(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share)

# find out the valid values for FileMode
PS> [Enum]::GetNames( [Io.FileMode] )
CreateNew
Create
Open
OpenOrCreate
Truncate
Append

# a couple different ways to invoke the method
PS> $stream = [Io.File]::Open('C:\MyFile.txt', 'OpenOrCreate')
PS> $stream = [Io.File]::Open('C:\MyFile.txt', [Io.FileMode]::OpenOrCreate)

Combining enum values

Another commonly-used feature of enums is their ability to be combined bitwise. Many enum types expose values which are meant to be used not just individually, but combined together to form combinations of options.

The RegistryRights enum is an example of an enum type which is frequently used in bitwise combinations.

# lots of possibilities!
PS> [Enum]::GetNames( [System.Security.AccessControl.RegistryRights] )
QueryValues
SetValue
CreateSubKey
EnumerateSubKeys
Notify
CreateLink
Delete
ReadPermissions
WriteKey
ExecuteKey
ReadKey
ChangePermissions
TakeOwnership
FullControl

Say I want to specify the rights to read and delete keys. I need to combine [System.Security.AccessControl.RegistryRights]::ReadKey and [System.Security.AccessControl.RegistryRights]::Delete. Powershell offers the standard bitwise operators, including bitwise OR, which can be used for this:

# equivalent of "RegistryRights.ReadKey | RegistryRights.Delete" in C#
PS> $readAndDelete = [System.Security.AccessControl.RegistryRights]::ReadKey -bor [System.Security.AccessControl.RegistryRights]::Delete

This approach has 2 drawbacks, however: 1. It’s very verbose. 2. Powershell bitwise operators only operate against numerical values, so the enum will actually get converted into its Int64 equivalent during this operation. This is unlikely to cause a bug in your script, but it’s not ideal.

A better solution is to take advantage of further built-in support for parsing enum values from strings. Powershell supports anything that can be handled by [Enum]::Parse( ), including bitwise combinations. Valid combination strings are “, “-delimited, e.g. “Value1, Value2, Value3”. So the combined value of ReadKey and Delete from System.Security.AccessControl.RegistryRights can be specified like this:

# much shorter!
PS> $readAndDelete = [System.Security.AccessControl.RegistryRights] "ReadKey, Delete"

Like single values, these strings can usually be passed to methods or cmdlets without any explicit casting or type information.

Tab completion (v3+)

It’s also worth mentioning that in Powershell v3+ the default tab-completion function has nice enum support. If a cmdlet or function takes an enum as a parameter, tab-completion will automatically cycle through the possible string values. This is nice, since you don’t need to look up possibilities beforehand.

function EnumTest
{
   param( [System.DayOfWeek] $Day )

   "Day is: $day"
}

PS> EnumTest -Day <TAB> # cycles through possible values for DayOfWeek enum type

Defining new enum types

Finally, let’s look at how to define a brand new enum type within your Powershell script. In order to do this, we need to resort to using a small bit of C#, but the syntax is very simple.  Add-Type makes it easy to add this new enum type to our session.

Here’s an example of creating the simplest style of enum:

Add-Type -TypeDefinition @"
   // very simple enum type
   public enum SimpleEnumType
   {
      Value1,
      Value2,
      Value3
   }
"@

PS> [SimpleEnumType]::Value1
Value1

If you don’t give enum names explicit values, they will be automatically numbered starting from 0, in the order defined:

PS> [SimpleEnumType]::Value1 -as [int]
0
PS> [SimpleEnumType]::Value2 -as [int]
1
PS> [SimpleEnumType]::Value3 -as [int]
2

But you are allowed to give explicit values if you like:

Add-Type -TypeDefinition @"
   // you can also define specific integer values for each enum value
   public enum ExplicitEnumType
   {
      None = 0,
      Value1 = 1,
      Value2 = 10,
      Value3 = 100
   }
"@
PS> [ExplicitEnumType]::Value2 -as [int]
10

And if you want to provide explicit support for bitwise combinations of values, it’s a good idea (though not technically required) to use the [Flags] attribute:

Add-Type -TypeDefinition @"
   // if using the Flags attribute, make sure to use powers of 2 so that bitwise combination works
   [System.Flags]
   public enum FlagsEnumType
   {
      None = 0,
      Value1 = 1,
      Value2 = 2,
      Value3 = 4
   }
"@

PS> [FlagsEnumType] "Value1, Value3"
Value1, Value3