Monday, November 2, 2009

Making Static .NET Classes More PowerShellish

There are many cases where you want to use static methods on .NET classes in PowerShell.  There are several classes that you may use quite a bit and would like to make them easier to use than having to write out [System.Math]::abs for example.  It’s quite easy to wrap these commands in a PowerShell function, but if there are many methods, it can be tedious and a pain to maintain.  So, I came up with the following approach to make this a bit easier.  I use the Math and System.IO.Path class methods quite a bit.  My example will look at the Math methods.
Functions with a Single Argument
In a script (called WrapMath.ps1) I declare the class
$class = 'System.Math'
and the list of methods…
$methods = 'abs','acos','atan','atan2','ceiling','cos','cosh','equals','exp',` 'floor','log','log10','sign','sin','sinh','sqrt','tan','tanh','trunc'
In this case, all of the above methods are ones that except one argument.  I then define a function prototype with place holders for the method and class
$Prototype = @'
<#
.Synopsis
|method| from static class |class|
.Description
|method| from static class |class|
.Example
|method| -2.56
.Link
[|class|]::|method|
#>
function global:|method|
{
$args[0] | foreach{return [|class|]::|method|($_)}
}
'@
You’ll notice the document string preceding the function declaration.  This actually works.  The next part is the slick bit…  loop over the methods, replacing |method| and |class| each time in the prototype and use Invoke-Expression to dynamically create the command.
$methods | foreach{            
Invoke-Expression (($Prototype.Replace('|method|',$_)).Replace("|class|",$class))            
}
You’ll notice I make use of a pipeline in the function prototype, so that these methods all work with an array or a single value.  So they are actually a bit more useful than just calling [system.math]::method
Functions with 2 Arguments
For functions that except two arguments you can do the following:
################################################################################            
#Methods that take two arguments.  The first argument can be an array.  The            
#second must be a single value.  So you could use the pow method to square            
#each element in an array.            
################################################################################            
$class = 'System.Math'                       
$methods = 'pow','round','IEEERemainder'                       
$Prototype = @'
<#
.Synopsis
|method| from static class |class|
.Description
|method| from static class |class|    
.Example
|method| 5.2 3
.Link
[|class|]::|method|
#>
function global:|method|
{
$parm2 = $args[1]
$args[0] | foreach{return [|class|]::|method|($_,$parm2)}
}
'@                       
$methods | foreach{            
Invoke-Expression (($Prototype.Replace('|method|',$_)).Replace('|class|',$class))            
}
Max and Min
Finally Max and Min are special cases, as they are some-what recursive
################################################################################            
#Max and Min methods.  These work for any size array of values.            
################################################################################            
$class = 'System.Math'                        
$methods = 'max','min'                        
$Prototype = @'
<#
.Synopsis
|method| from static class |class|
.Description
|method| from static class |class|    
.Example
|method| 10,12,13,14
.Link
[|class|]::|method|
#>
function global:|method|
{
$retval = $args[0][0]
$args[0] | foreach{$retval = [|class|]::|method|($_,$retval)}
return $retval
}
'@                        
$methods | foreach{            
Invoke-Expression (($Prototype.Replace('|method|',$_)).Replace('|class|',$class))              
}
I’ve done a similar thing with the System.IO.Path class that I will write about later.  Hope this helps someone out.