Monday, July 27, 2009

Broken New-Enum in Powershell v2

I LOVE using enums in Powershell. If you don’t know about enumerations, you should definitely learn to use them (What the heck is an ENUM?)

I will try to describe it in my words. Enums allows you to define set of allowed arguments. Consider example when I am working with a technology that considers 0 as Write, 1 as Read and 2 as ReadWrite. You will remember that while you work with it, but what if you return back to your own scripts after one year?

Normally you would write following script:

Function Set-Security ([int]$Access = 1) {…}

$Access (as you probably already knows) means which permissions you want to give and by default we set it to Read. Your code will then follow with checking if user provided correct input

Switch ($Access) {
0 {“Write”}
1 {“Read”}
2 {“ReadWrite”}
Default {“Not supported”}
}

Later on you will decide that you want to add other type (Custom) and you need to rewrite all your scripts that are using this syntax. Not talking about fact that you require from users of your scripts to use techie language and use numbers instead of words.

Instead what you can do is to create following:
New-Enum MyAccess Write Read ReadWrite (function New-Enum was taken from Powershell blog)

Now your function can be simplified:
Function Set-Security ([MyAccess]$Access) {}

You can call your function using following syntax now:
Set-Security –Access 0
Set-Security –Access “Write”
Set-Security –Access [MyAccess]::Write

If you want to extend your enumeration, you simply add new enumeration and all your functions will support it out of the box.

Using enumerations is especially useful in case you create complex scripts – for example if you export some objects to XML and import it later on. You simply specify [string]$X = [MyAccess]$Var

In your XML file, instead of non-sense numbers you will get nice string values. Import afterwards is done same way: [MyAccess]$Var = $X. I got very good experiences with this approach especially when using COM based environments like MFCOM.

Ok, so are you into enumerations? They are perfect – however they doesn’t work in Powershell v2 :( New-Enum function works correctly and doesn’t return any error, however enumerations are not created. If you run SAME New-Enum twice, then everything works as expected.

I got my slightly modified version of New-Enum that works on Posh V2 also:

function Global:S4M\New-Enum ([string] $name, [switch]$FixMode) { 

$appdomain = [System.Threading.Thread]::GetDomain()
$assembly = new-object System.Reflection.AssemblyName
$assembly.Name = "EmittedEnum"
$assemblyBuilder = $appdomain.DefineDynamicAssembly($assembly,
[System.Reflection.Emit.AssemblyBuilderAccess]::Save -bor [System.Reflection.Emit.AssemblyBuilderAccess]::Run);
$moduleBuilder = $assemblyBuilder.DefineDynamicModule("DynamicModule", "DynamicModule.mod");
$enumBuilder = $moduleBuilder.DefineEnum($name, [System.Reflection.TypeAttributes]::Public, [System.Int32]);
for($i = 0; $i -lt $Args.Length; $i++) {
If (([string]($Args[$i])).Contains("=")) {
[string]$EnumName = [string](($Args[$i].Split("="))[0])
$Null = $enumBuilder.DefineLiteral($EnumName, [int]($Args[$i].Split("="))[1]);
} Else {
$Null = $enumBuilder.DefineLiteral($Args[$i], $i);
}
}
#Used to fix issue with Powershell v2
If ($Host.Version.Major -eq 1 -or $FixMode) {
$enumBuilder.CreateType() > $Null;
} Else {
S4M\New-Enum -FixMode -Name $Name @Args
$enumBuilder.CreateType() > $Null;
}
}



This version will automatically detect which version are you running and in case you don’t run Powershell V1, it will automatically re-run New-Enum once more. Interesting is usage of @Args instead of $Args – I will blog about this next time ;)



This function contains 2 changes to original:



1.) You can assign special values to enumerations:




New-Enum MyEnum Test=1 Test2=2 Test3=256




2.) Powershell v2 compatibility is fixed

1 comment:

PhaedrusInExile said...

Thanks, I thought I was having a scope issue not that it was powershell hiding something from me.