Introduction

Let’s start by explaining what an Enum is. An Enum, short for enumeration, is a data type that groups a collection of constants. It is used to assign a limited set of possible values to a variable. This can greatly improve the readability and maintainability of your code.

Grouping Constants

Enums allow you to group related constants under a single type. A good example is the built-in PowerShell enum System.DayOfWeek, which contains all the days of the week grouped together.

Preventing Errors

Since you can only use predefined values, the risk of introducing errors by entering invalid values is significantly reduced.

Improving Readability

This point has two aspects. The first reason readability improves is by avoiding magic numbers or strings, which I’ll cover in the next section.

The second reason relates to databases and storage. A string takes up more space in a database than an integer. With enums, you can work with readable strings in your code while outputting integers to the database.

Avoiding Magic Numbers or Strings

First, let’s clarify what a magic number or string is. We’ve all seen hardcoded values in code, and often it’s not immediately clear what those values represent. Take this example:

if ($age -lt 18) {}

Where does the number 18 come from? For many people in the Netherlands, this value might make sense, but for someone in the USA, it could be confusing. Here, I intended to represent the legal drinking age. If instead of hardcoding 18, I created a variable like this:

$LegalDrinkingAge = 18
if ($age -lt $LegalDrinkingAge) {}

Now it’s immediately clear what the condition means.

So how can enums help avoid magic numbers or strings? We can create an enum that includes different types of drinks, such as those served at the bar we’re scripting for, and assign the appropriate age to each.

$LegalDrinkingAge = 18
$AllAges = 0
$Puberty = 12

$enumDef = @"
public enum DrinkType {
    Beer = $LegalDrinkingAge,
    Wine = $LegalDrinkingAge,
    Spirits = $LegalDrinkingAge,
    SoftDrink = $AllAges,
    Coffee = $Puberty
}
"@

Add-Type -TypeDefinition $enumDef

# Example:
[int][DrinkType]::Beer    # Result: 18

Now you can compare an age against a DrinkType value.

How to Define Enums in PowerShell

In PowerShell, you can define enums in three different ways, each with its own pros and cons. Let’s compare them:

Native Enum Syntax

enum LogLevel { Info; Warning; Error }

Advantages

This approach is simple and highly readable. It’s created directly in PowerShell without extra complexity. You also get strong type binding, which means autocompletion and type-checking are available. Since it doesn’t rely on .NET compilation, this method is well-suited for scripts and modules.

Disadvantages

The downside is that it’s only available in (Windows) PowerShell version 5 and later. However, this is rarely an issue today. Additionally, you can’t assign custom values or add extra functionality like methods or inheritance.

Add-Type with C# Enum

Add-Type -TypeDefinition @"
public enum LogLevel {
    Info = 1,
    Warning = 2,
    Error = 3
}
"@

Advantages

This method gives you the full power of .NET. You can assign integer values, add attributes, and even include XML documentation. It also works on older PowerShell versions (v2 and above), which is useful in environments where upgrading isn’t possible. It’s flexible too: you can define multiple complex types in a single Add-Type block.

Disadvantages

The drawback is the extra boilerplate since you’re writing C# code inside a string. The first execution is slightly slower due to the compilation step. It also feels less “PowerShell-native,” which can make it less intuitive for beginners.

Class with Static Properties (Pseudo-Enum)


class LogLevel {
    static [string]$Info = 'Info'
    static [string]$Warning = 'Warning'
    static [string]$Error = 'Error'
}

Advantages

This approach is extremely flexible. You can easily add methods, implement validation, and include additional logic. Since you’re not limited by enum restrictions, you can work with strings, integers, or even custom objects. It fits well with modern PowerShell (v5+) and leverages native OOP features.

Disadvantages

It’s not a true enum, so you don’t get type-checking or autocompletion like with real enums. This increases the risk of type errors because you’re essentially working with plain strings. For simple cases, this method can also feel like overkill since it requires more code than necessary.

Lets recap

  • Native Enum: Best for simple, strongly typed values with autocompletion.
  • Add-Type Enum: Powerful and backward-compatible, but requires more boilerplate.
  • Class with Static Properties: Highly flexible, but lacks type-checking and autocompletion.
FeatureNative Enum (PowerShell)Add-Type Enum (C#)Class with Static Properties
AvailabilityPowerShell 5+PowerShell 2+PowerShell 5+
Type-checking✅ Yes✅ Yes❌ No
Autocompletion✅ Yes✅ Yes❌ No
Flexibility❌ Limited (values only)✅ High (methods, docs)✅ High (methods, validation)
Complexity✅ Simple and readable❌ More boilerplate❌ More code for simple cases
Performance✅ Fast❌ Slower on first run✅ Fast
Extra functionality❌ None✅ Attributes, XML docs✅ Methods, inheritance possible
Script-friendly✅ Yes❌ Less ideal (compilation)✅ Yes
Risk of type errors❌ Low❌ Low✅ High (strings)

How to Use Enums

Now that we know how to create enums, let’s look at how to use them.

# Direct reference
[LogLevel]::Warning  # Result: Warning

# Get the integer value
[int][LogLevel]::Warning  # Result: 1

# And the reverse
[LogLevel]2  # Result: Error

# If statement example
$LineFromLog = "2025-11-13 10:05:12 [ERROR] Config missing"
if ($LineFromLog -match [LogLevel]::Error) {
    Write-Output "We encountered an error!"
} else {
    Write-Output "All is fine!"
}
# Result: We encountered an error!

One example I cannot demonstrate here is using enums during coding with tab-completion. Once the enum is defined, you can easily switch between the available values using IntelliSense in your editor.

Enums in Functions

Building on the previous example of tab-completion, using enums in a function provides two major benefits. First, you avoid magic values for default parameters. Second, you get built-in validation because you cannot pass a value that is not part of the enum.

function Write-Log {
    param(
        [string]$Message,
        [LogLevel]$Level = [LogLevel]::Info
    )

    $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    Write-Host "$timestamp [$Level] $Message"
}

Write-Log -Message "Application started" -Level Info
# Result: 2025-11-13 13:22:55 [Info] Application started

If you test the example above, you will notice that tab-completion works for all values defined in the LogLevel enum.

How to Read All Values from an Enum

One challenge I often run into is forgetting which values or options an enum contains. It’s useful to know how to easily retrieve these values. Here’s how you can do it:

[Enum]::GetNames([LogLevel])
Info
Warning
Error

Dynamically Generating Enums

Sometimes it’s useful to create an enum based on runtime data, such as configuration values or a list from a database. Although PowerShell does not have native support for dynamic enum creation, we can achieve this using Add-Type.

$values = 'Low','Medium','High'
$enumDef = @"
public enum Priority {
$(($values | ForEach-Object { $_ }) -join "`n,")
}
"@
Add-Type -TypeDefinition $enumDef

# Usage:
[Priority]::High

We start with a list of values we want to use, then create the Priority enum. If you compare this example with the one shown earlier for creating an enum using Add-Type, you’ll notice that we first build the TypeDefinition in a variable and then pass it to Add-Type. This is necessary because C# and here-strings do not handle variables directly inside the type definition.

It’s also important that each value is separated by a comma. Since C# allows a trailing comma, you could add the comma inside the ForEach-Object loop, but in this example, I chose to handle it in the -join operation. Both approaches are valid and accepted.

Flags Enums

Flags enums are ideal when you want to combine multiple options using bitwise operations. They are commonly used for permissions, settings, or status values. A flags enum is an enum where each value represents a bitmask (usually a power of 2). You can combine values using bitwise OR (-bor) and check them using bitwise AND (-band).

Add-Type -TypeDefinition @"
[System.Flags]
public enum FileAccess {
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4
}
"@

# Combine permissions
$permissions = [FileAccess]::Read -bor [FileAccess]::Write
$permissions   # Output: Read, Write

# Check if Write access is granted
if (($permissions -band [FileAccess]::Write) -ne 0) {
    "Write access is granted"
}

It is important not to forget the [Flags] or [System.Flags] attribute when creating the enum. If you omit it, you will get the combined numeric value instead of the names. In the example above, that would be 3.

If you create the enum using the native PowerShell syntax, you cannot use [Flags], and therefore you cannot display combined names:

enum FileAccess {
    None = 0
    Read = 1
    Write = 2
    Execute = 4
}

# Combine
$perm = [FileAccess]::Read -bor [FileAccess]::Execute
[int]$perm   # Output: 5

# Check if Write access is granted
if (($permissions -band [FileAccess]::Write) -ne 0) {
    "Write access is granted"
}

Best Practices for Flags Enums

When working with Flags enums, it’s important to follow a few key principles to ensure clarity and functionality. Assign values using powers of two, such as 1, 2, 4, 8, and so on, so that combinations can be represented through bitwise operations. Always include a None option with a value of 0 to represent the absence of flags. Finally, when defining the enum with Add-Type, make sure to apply the [Flags] attribute so that multiple values can be combined correctly.

Performance

Enums are not just syntactically elegant; they are also faster than string comparisons. Internally, an enum value is stored as an integer, which makes comparisons between enums significantly quicker than comparing strings. This difference becomes noticeable when performing frequent checks, such as iterating over thousands of items in a loop.

While the performance gain might not always be dramatic, compile-time checks reduce the likelihood of typos and invalid values. This leads to a smoother experience when writing and running your scripts or code.

Summary

Enums in PowerShell provide a powerful way to make your scripts more robust, readable, and maintainable. By grouping related constants under a single type, you reduce the risk of errors, avoid magic numbers or strings, and improve clarity throughout your code. PowerShell offers several ways to define enums: the native syntax for simplicity, Add-Type for full .NET flexibility and backward compatibility, and classes with static properties for maximum customization.

Using enums in functions adds built-in validation and enables tab-completion, making your scripts easier to write and less error-prone. You can also retrieve all enum values programmatically, which is helpful when working with dynamic logic. For Flags enums, follow best practices such as using powers of two, including a None value, and applying the [Flags] attribute when using Add-Type.

In short, enums are a simple yet effective tool to bring structure and reliability to your PowerShell scripts. Whether you’re writing quick automation or building reusable modules, enums help you write cleaner, more predictable code.

Leave a Reply

Your email address will not be published. Required fields are marked *