- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
Great developers (and scripters) know it's not good practice to repeat yourself in code. Repeating yourself may work in the short-term, but as the code base becomes larger, it soon turns into a management nightmare. Having to change the same variable in 20 different places is not fun! To prevent this code management nightmare, it's important to adopt good practices that allow you to make one change in one spot. One way to do this is by using enumerated types, or enums.
Although enums have multiple applications in various languages, I've found that they are most useful when writing code that prevents duplication. Let me explain.
First of all, what is an enum anyway? Enums, in fact, are simple constructs in any programming language. They are just a list of items in a collection. They are a way of setting a predefined set of items that are associated with a common type. I know that might be hard to grasp, so let's look at an example.
Enums allow me to create a set of predefined items ahead of time. Once I have this set created, I can then implement it in different ways. For this article, I'll go over a great use case for enums.
Let's say I have a script that I've created to give to my helpdesk. This script creates Active Directory users. I want to limit in which OUs this script can create users. To do this, I can define an enum of allowed OUs. To create an enum in PowerShell 5.0, we use the enum keyword followed by each item that's a part of that enum.
enum AllowedOUs { CompanyUsers Accounting HR IT }
Here you can see I have an enum called AllowedOUs with four items inside. Once I do this, I can then reference all of the items in this enum by using two colons after specifying the AllowedOUs enumerated type. You can see below that the ISE knows what items are already in this enum, and Intellisense is kicking in to give me the list.
Note: There are a few gotchas with enums. First, the items in the enum cannot have spaces. So, don't even try to surround them with quotes; it's not going to work. In addition, enums cannot have special characters except an underscore. Just remember that the items are just simple strings.
Now that I have created the enum, how can I use it to restrict my helpdesk users from adding users into any OU they like? To demonstrate, let's start with a simple function template to do this.
function New-CompanyXActiveDirectoryUser { param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$UserName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$FirstName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$LastName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$OrganizationalUnit ) $params = @{ Name = $UserName SamAccountName = $UserName GivenName = $FirstName SurName = $LastName Path = $OrganizationalUnit Server = 'MYDC' Enabled = $true } New-Aduser @params }
You'll see that I'm building a helper function around the New-AdUser cmdlet. This gives me the ability to not only restrict the input to the cmdlet but to also provide some default values. However, anyone using this function can pass anything they want to the OrganizationalUnit parameter. Let's fix that by using the enum that we created earlier.
[Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [AllowedOUs]$OrganizationalUnit
Notice the simple change? I just changed the type from string to AllowedOUs. You can see below that the ISE again knows that this is an enum and provides us with list of items, just like the ValidateSet parameter validation attribute does.
If I try to pass something other than an item in the enum, I'll immediately get an error and be presented with a list of allowable items.
You might think, "Big whoop! I can do that with the ValidateSet parameter validation attribute." True, but do you recall my intro where I mentioned not to repeat yourself? What if you had another function that removed an AD user and that same OU restriction needs to be placed there as well? At that point, you'd have two functions with this exact same parameter:
[Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [ValidateSet('CompanyUsers','HR','Accounting','IT')] [string]$OrganizationalUnit
Now you've got duplicate references, and if another OU needs to be added, it would have to be done in two different spots. But an enum can be used in multiple places. Simply set the parameter type to [AllowedOUs] in as many functions as you like, and they will all follow the same pattern.
Subscribe to 4sysops newsletter!
function Function1 { param( [AllowedOUs]$OrganizationalUnit ) } function Function2 { param( [AllowedOUs]$OrganizationalUnit ) }
You can see now that using enums is not only an excellent way to organize like items but is also a great way to create a shared ValidateSet validation attribute as well!
In which file would the definition of the enum be saved?
The enum could be created in any file you wish as long as it’s in the same scope as what you’re using. For modules, I’ll typically declare the enums at the top and then reuse them throughout. For scripts, you could do the same as well.
In the first example you prepared a hash table for parameters splatting but after that you assigned values to parameters directly.
Is it your oversight?
Yep. Got it fixed. Thanks!
If I am writing a self-contained script I cannot use enum to delineate parameters. Here’s a small sample…
The parameter expansion does not work. I can’t get it to work. Any ideas?
@Henry
You must place the Enum section before you use it with the Param section.
Here is an example which works:
But that wouldn’t expose the enum to the PS CLI. I’m looking for a way to use the enum in the PS CLI but specify it in the script. That’s only doable with ValidateSet() as far as I can see. But that’s OK, if I’m writing a stand-alone one shot script I don’t really need to reuse an enum. I really just want to restrict input.
@henry
Yes that’s correct.
The enum is only avaibale in the context where it has been created.
In your case the ValidateSet attribute is probably more appropriate.
An enum can be useful for a module containing several functions which are using the same lists.