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.

Friday, October 30, 2009

PowerShell 2.0 RTM and WPK

So after several months of waiting PowerShell 2.0 for XP and Vista was released rather cryptically. It was released as part of the "Windows Management Framework on Windows XP, Windows Server 2003, Windows Vista, and Windows Server 2008." It is available for download here. I would have expected some sort of better announcement or explanation... canon fire or something. Oh well, it's here!

PowerShellPack
Microsoft has also released PowerShellPack. A collection of handy add ons to enhance your PowerShell experience. Included in PoweShellPack is a module called WPK, which bears a lot of resemblence to PowerBoots, that I have blogged about previously.  Here is a list of features in the PowerShellPack from the Microsoft PowerShell blog
@"
WPK
Create rich user interfaces quick and easily from Windows PowerShell. Think HTA, but easy. Over 600 scripts to help you build quick user interfaces.  To get started learning how to write rich WPF UIs in script, check out Writing User Interfaces with WPK.
IsePack
Supercharge your scripting in the Integrated Scripting Environment with over 35 shortcuts. TaskScheduler
List scheduled tasks, create or delete tasks
FileSystem
Monitor files and folders, check for duplicate files, and check disk space
DotNet
Explore loaded types, find commands that can work with a type, and explore how you can use PowerShell, DotNet and COM together
PSImageTools
Convert, rotate, scale, and crop images and get image metadata
PSRSS
Harness the FeedStore from PowerShell
PSSystemTools
Get Operating System or Hardware Information
PSUserTools
Get the users on a system, check for elevation, and start-processaadministrator
PSCodeGen
Generates PowerShell scripts, C# code, and P/Invoke

"@

Testing out WPK
So decided to give WPK a spin and do a simple image preview that I have done previously. It has left me scratching my head quite a bit.

The following works:
$width = 600        
$images = (ls *.jpg,*.png) | %{new-image $_.fullname -Width $width -Tag $_.Name}        
$text = $images |  %{New-Label $image.Tag -Width $width -FontSize 16}        
new-wrappanel {                     
  foreach($i in (0..($images.Count-1))){                                                           
    new-stackpanel {        
      $text[$i]        
      $images[$i]        
    }         
  }                            
}  -show        

While this does not:
function test-func        
{        
$width = 600        
$images = (ls *.jpg,*.png) | %{new-image $_.fullname -Width $width -Tag $_.Name}        
$text = $images | %{New-Label $image.Tag -Width $width -FontSize 16}        
new-wrappanel {                     
  foreach($i in (0..($images.Count-1))){                                                           
    new-stackpanel {        
      $text[$i]        
      $images[$i]        
    }         
 }                            
}  -show        
}        
test-func
 
The only difference between the two is that in the latter case the WPK calls are wrapped in a function. That's it. I'm totally confused as to why this might be happening.  This requires some head scratching. 

I'm curious to see how WPK and PowerBoots evolve in relation to each other.

Tuesday, June 16, 2009

Using WPF in PowerShell with PowerBoots

I got the bug to play with WPF a couple of weeks ago. I'm relatively familiar with Windows Forms, and thought I'd like to finally look at WPF. I downloaded VC# 2008 Express and messed around a little bit for a few days. I had the goal of writing a WPF program that would host powershell and act like a simple Matlab. Well, I ran across PowerBoots, written by Joel Bennet (http://huddledmasses.org/powerboots/). PowerBoots brings WPF to PowerShell with some extremely simple syntax. After playing with it for a day, I wrote this relatively simple preview-images function that provides an experience similar to the image "Preview" in XP. For a tutorial see http://huddledmasses.org/powerboots-tutorial-walkthrough/


I found a very nice online source code formatter to format the below code: http://www.manoli.net/csharpformat/

The result looks like this:




And Here is the code:


function global:preview-images
{
#by default load all images supported by the WPF image control (System.Windows.Controls.Image)
param($images = (ls *.bmp,*.gif,*.ico,*.jpg,*.png,*.wdp,*.tiff,*.tif ))

$count = $images.length
$global:images = $images
$global:i = 0
$global:imindex = 1 #index of the image control in the dockpanel children array

boots -left 100 -top 100 -width 1024 -title "Preview - $pwd" {

stackpanel{
wrappanel {
#0
button "<--" -FontSize 24 -width 50 -On_Click {
$global:i = 0
$this.parent.parent.children[1].children[$global:imindex].source = $images[$global:i].fullname
$block[0].Inlines.Clear();
$block[0].Inlines.Add($images[$global:i].name)
}
#1
button "<" -FontSize 24 -width 50 -On_Click {
if ($global:i -gt 0) {
$global:i--
}
else {
$global:i = $images.length-1
}
$this.parent.parent.children[1].children[$global:imindex].source = $images[$global:i].fullname
$block[0].Inlines.Clear();
$block[0].Inlines.Add($images[$global:i].name)
}
#2
button ">" -FontSize 24 -width 50 -On_Click {
if ($global:i -lt ($images.length-1)) {
$global:i++
}
else {
$global:i = 0
}
$this.parent.parent.children[1].children[$global:imindex].source = $images[$global:i].fullname
$block[0].Inlines.Clear();
$block[0].Inlines.Add($images[$global:i].name)
}
#3
button "-->" -FontSize 24 -width 50 -On_Click {
$global:i = $count - 1
$this.parent.parent.children[1].children[$global:imindex].source = $images[$global:i].fullname
$block[0].Inlines.Clear();
$block[0].Inlines.Add($images[$global:i].name)
}
#4
button "END" -FontSize 24 -width 75 -On_Click {
#$global:i = $count
$this.parent.parent.parent.close()
#$block[0].Inlines.Clear();
#$block[0].Inlines.Add( $images[$global:i].name)
}
#5
button "Image List" -FontSize 20 -On_Click {
$images | foreach{write-host $_.name}
}
#6
textblock " " -FontSize 30 -FontWeight bold -Foreground "#0000FF"
#7
textblock $images[$global:i].name -OutVariable script:block -FontSize 30 -FontWeight bold -Foreground "#0000FF"
}
dockpanel -lastchildfill $true {

$images | %{$_.name} | listbox -SelectedIndex 0 -maxheight 900 -On_MouseDoubleClick{
$global:i = $this.selectedIndex
$this.parent.children[$global:imindex].source = $images[$global:i].fullname
$block[0].Inlines.Clear();
$block[0].Inlines.Add($images[$global:i].name)
} | ForEach-Object { $_.SetValue(
[System.Windows.Controls.DockPanel]::DockProperty,
[System.Windows.Controls.Dock]::left)
$_
}
image -source $images[$i].fullname
}

}

} -Background (linearGradientBrush $(GradientStop -Offset 0 -Color "#ffffff";GradientStop -Offset 1 -Color "#222222"))
}