It is possible to assemble .NET methods with CIL opcodes (i.e. .NET bytecode) in PowerShell in only a few lines of code using dynamic methods and delegates.
I’ll admit, I have a love/hate relationship with PowerShell. I love that it is the most powerful scripting language and shell but at the same time, I often find quirks in the language that consistently bother me. One such quirk is the fact that integers don’t wrap when they overflow. Rather, they saturate – they are cast into the next largest type that can accommodate them. To demonstrate what I mean, observe the following:
You’ll notice that [Int16]::MaxValue (i.e. 0x7FFF) understandably remains an Int16. However, rather than wrapping when adding one, it is upcast to an Int32. Admittedly, this is probably the behavior that most PowerShell users would desire. I, on the other hand wish I had the option to perform math on integers that wrapped. To solve this, I originally thought that I would have to write an addition function using complicated binary logic. I opted not to go that route and decided to assemble a function using raw CIL (common intermediate language) opcodes. What follows is a brief explanation of how to accomplish this task.
Common Intermediate Language Basics
CIL is the bytecode that describes .NET methods. A description of all the opcodes implemented by Microsoft can be found here. Every time you call a method in .NET, the runtime either interprets its opcodes or it executes the assembly language equivalent of those opcodes (as a result of the JIT process - just-in-time compilation). The calling convention for CIL is loosely related to how calls are made in X86 assembly – arguments are pushed onto a stack, a method is called, and a return value is returned to the caller.
Since we’re on the subject of addition, here are the CIL opcodes that would add two numbers of similar type together and would wrap in the case of an overflow:
IL_0000: Ldarg_0 // Loads the argument at index 0 onto the evaluation stack.
IL_0001: Ldarg_1 // Loads the argument at index 1 onto the evaluation stack.
IL_0002: Add // Adds two values and pushes the result onto the evaluation stack.
IL_0003: Ret // Returns from the current method, pushing a return value (if present) from the callee's evaluation stack onto the caller's evaluation stack.
Per Microsoft documentation, “integer addition wraps, rather than saturates” when using the Add instruction. This is the behavior I was after in the first place. Now let’s learn how to build a method in PowerShell that uses these opcodes.
Dynamic Methods
In the System.Reflection.Emit namespace, there is a DynamicMethod class that allows you to create methods without having to first go through the steps of creating an assembly and module. This is nice when you want a quick and dirty way to assemble and execute CIL opcodes. When creating a DynamicMethod object, you will need to provide the following arguments to its constructor:
1) The name of the method you want to create
2) The return type of the method
3) An array of types that will serve as the parameters
The following PowerShell command will satisfy those requirements for an addition function:
$MethodInfo = New-Object Reflection.Emit.DynamicMethod('UInt32Add', [UInt32], @([UInt32], [UInt32]))
Here, I am creating an empty method that will take two UInt32 variables as arguments and return a UInt32.
Next, I will actually implement the logic of the method my emitting the CIL opcodes into the method:
$ILGen = $MethodInfo.GetILGenerator()
$ILGen.Emit([Reflection.Emit.OpCodes]::Ldarg_0)
$ILGen.Emit([Reflection.Emit.OpCodes]::Ldarg_1)
$ILGen.Emit([Reflection.Emit.OpCodes]::Add)
$ILGen.Emit([Reflection.Emit.OpCodes]::Ret)
Now that the logic of the method is complete, I need to create a delegate from the $MethodInfo object. Before this can happen, I need to create a delegate in PowerShell that matches the method signature for the UInt32Add method. This can be accomplished by creating a generic Func delegate with the following convoluted syntax:
$Delegate = [Func``3[UInt32, UInt32, UInt32]]
The previous command states that I want to create a delegate for a function that accepts two UInt32 arguments and returns a UInt32. Note that the Func delegate wasn't introduced until .NET 3.5 which means that this technique will only work in PowerShell 3+. With that, we can now bind the method to the delegate:
$UInt32Add = $MethodInfo.CreateDelegate($Delegate)
And now, all we have to do is call the Invoke method to perform normal integer math that wraps upon an overflow:
$UInt32Add.Invoke([UInt32]::MaxValue, 2)
Here is the code in its entirety:
For additional information regarding the techniques I described, I encourage you to read the following articles:
Introduction to IL Assembly Language
Reflection Emit Dynamic Method Scenarios
How to: Define and Execute Dynamic Methods