PowerBoots: Loading XAML Windows in PowerShell 1.0 or 2.0

Awhile back I wrote a series of posts about WPF From PowerShell From PowerShell” which were about how you could load XAML in previous PowerShell 2 CTPs to create WPF user interfaces … a few people have mentioned loading XAML in PowerBoots, and a couple of people have posted other samples showing XAML even since I published the most recent release, so I figure it’s time to point out that you really can load that XAML into Boots, and get all the threading and other support.

Just for fun, I’m going to rehash an earlier post about updating windows to show how you can go about this using PowerBoots, and hopefully show that it’s a little easier (and a lot more async). Compare and contrast the code in this article with that one, just for fun.

This works with any version of PowerShell

Unlike the original article, most this code (except where explicitly mentioned) works on either PowerShell v2 with PowerBoots, or PowerShell 1.0 with PoshWPF, a snapin that is part of the PowerBoots module but is also released separately… Also, unlike those previous posts, this does not require you to be running PowerShell.exe with the -STA switch, since the New-BootsWindow cmdlet takes care of threading for us.

The simple splash screen


$Splash = New-BootsWindow -Async -Passthru -SourceTemplate @"
<window xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        windowstyle="
None" allowstransparency="True" opacity="0.8"
        topmost="
True" sizetocontent="WidthAndHeight"
        windowstartuplocation="
CenterOwner" showintaskbar="False">
   <img source="
http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/40000/1000/200/41215/41215.strip.print.gif"
        height="
177">
</window>
"
@ -Content {$Args[0].Content} -On_MouseDown { $this.DragMove() }
# Imagine this is your script, working ...
Start-Sleep 3
# And now you're done, and want to close it
Invoke-BootsWindow $Splash { $Splash.Close() }

This is, of course, not a great example of why you would want to use XAML, since it reads virtually the same as it would if you wrote it in PowerBoots (if you were on PowerShell v2):


## This requires PowerBoots which is (as of this writing) is still v2 only...
$window = Boots -Async -Passthru  -Content {
   Image -Height 177 -Source http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/40000/1000/200/41215/41215.strip.print.gif
} -WindowStyle None -AllowsTransparency $True -Opacity 0.8 -Topmost $True -WindowStartupLocation CenterOwner -ShowInTaskbar $False -On_MouseDown { $this.DragMove() }

# Imagine this is your script, working ...
Start-Sleep 3
# And now you're done, and want to close it
Invoke-BootsWindow $Splash { $Splash.Close() }

The big difference, of course, is that this is fully aync, so unlike the splash screen in the original post, we don’t have to do any tricks to get it to render, and we are able to add a MouseDown handler so you can drag the splash screen around while you wait for it to go away…

And now, for something completely different

Let’s skip over the very useful information I already wrote about the properties you can set on the “Window” object in WPF (all of which are parameters to New-BootsWindow, by the way) and cut straight to the second example … I’ve tweaked an old WPF sample I had and ported it to PowerShell, and now, adapted it for PoshWpf ... the xaml file is here and I’ll show you the code just as soon as you take a look at the screenshot (trust me, the code will make more sense if you’ve seen the output):

A clock, with working CPU and RAM bars

So basically, the UI is defined in a href=‘http://HuddledMasses.org/wpf-from-powershell-updating-windows/clock/’ rel=‘attachment wp-att-528’>that xaml file, and all we need to do in the script is update the time, update the CPU bar, and update the RAM bar…

Please notice the content block

PowerBoots passes the window object itself into the content scriptblock, and the content block must output the content you want in the window. Of course, if you’re loading a fully defined window in XAML, you don’t want to change the Window’s content, but you have to return something, so you should just return … the content that’s already in the window. Ie: $Args[0].Content

One other point, for those of you who are going to become XAML masters. In PowerBoots you can define just the styles and templates as resources in a separate XAML file which you can load with the Add-BootsTemplate, and then you can still create the UI in XAML or useing PowerBoots syntax. In my case, this XAML was edited using kaxaml (it could have been Expression Blend, or Visual Studio), and kaxaml doesn’t make it easy to separate out resource files, so I just left it all in one file:


Write-Host "Initializing Performance Counters, please have patience" -fore Cyan
### Import PoshWpf module
Import-Module PowerBoots
### Or, on v1:
# Add-PSSnapin PoshWpf

$script:cpu = new-object System.Diagnostics.PerformanceCounter "Processor", "% Processor Time", "_Total"
$script:ram = new-object System.Diagnostics.PerformanceCounter "Memory", "Available KBytes"

## get initial values, because the counters don't work until the second call
$null = $script:cpu.NextValue()
$null = $script:ram.NextValue()
$script:maxram = (gwmi Win32_OperatingSystem).TotalVisibleMemorySize

Write-Host "Loading XAML window..." -fore Cyan
## Load the XAML and show the window. It won't be updating itself yet...
$clock = New-BootsWindow -Async -Passthru -Content { $Args[0].Content
} -FileTemplate "C:\Users\Joel\Documents\WindowsPowerShell\Scripts\Demo\clock.xaml"

## Create a script block which will update the UI by changing the Resources!
$counter = 0;
$updateBlock = {
   # Update the clock
   $clock.Resources["Time"] = [DateTime]::Now.ToString("hh:MM.ss")

   # We only want to update the counters at most once a second
   # Otherwise their values are invalid and ...
   # The CPU counter fluctuates from 0 to the real number
   if( $counter++ -eq 4 ) {
      $counter = 0
      # Update the CPU counter with the absolute value and the percentage
      $cu = $cpu.NextValue()
      $clock.Resources.CpuP = ($cu / 100)
      $clock.Resources.Cpu = "{0:0.0}%" -f $cu
      # Update the RAM counter with the absolute value and the percentage
      $rm = $ram.NextValue()
      $clock.Resources.RamP = ($rm / $maxram)
      $clock.Resources.Ram = "{0:0.00}Mb" -f ($rm/1MB)
      }
}

## Now we need to call that scriptblock on a timer. That's easy, but it
## must be done on the window's thread, so we use Invoke-BootsWindow.
## Notice the first argument is the window we want to run the script in
Invoke-BootsWindow $clock {
   ## We'll create a timer
   $global:timer = new-object System.Windows.Threading.DispatcherTimer
   ## Which will fire 4 times every second
   $timer.Interval = [TimeSpan]"0:0:0.25"
   ## And will invoke the $updateBlock
   $timer.Add_Tick( $updateBlock )
   ## Now start the timer running
   $timer.Start()
}

## And just like that, the $UpdateBlock is running 4x a second
## and the clock is working.  Pretty cool, right?
## what's really cool is ... we still have full control in the console
## so we can add these events afterward:

## If we wanted to, say, handle mouse events to let you drag the window or close it ...
Invoke-BootsWindow $clock {
   $clock.Add_MouseLeftButtonDown( {
      $_.Handled = $true
      $clock.DragMove() # WPF Magic!
   } )
   $clock.Add_MouseRightButtonDown( {
      $_.Handled = $true
      $timer.Stop()  # we'd like to stop that timer now, thanks.
      $clock.Close() # and close the windows
   } )
}
 

So, meet Register-BootsEvent

If you’ve read the previous PowerBoots articles, you know that you can specify events as parameters to the PowerBoots functions, like -On_Click or -On_MouseLeftButtonDown, but in the case where you’re loading XAML, you can’t specify events in the XAML (WPF’s XamlReader won’t read XAML with events on it, or even with the Class specified, because you can’t use the associated code file anyway).

In any case, what I’m adding to the next version of PowerBoots/PoshWpf are two functions:

  1. Find-BootsControl takes a boots window and the Name of a control, and will (try to) find the named control. Of course, for that to work, you need to know the name of the control (your best bet is to name it yourself, but in the next version I may also issue names for elements as they are created if you don’t specify the name).
  1. Register-BootsEvent takes either a control, OR a boots window and control name, plus an event name and a ScriptBlock. If you pass the window and name, it calls Find-BootsControl to find the control; once it has a control, it registers the ScriptBlock as a handler for the specified event.

For now, both of these require that you’ve assigned a name to the control, but I’m working on setting things up so controls would be automatically named (based on their type, as with the old WinForms designer: like Button1, Button2, etc.), or allowing an XPath-like selection without named controls. In any case, this will work nicely for generating event handlers for WPF imported from XAML, and you can even specify the element directly (ie: if you don’t specify the -Name parameter, the event will be hooked on whatever element you pass in) so that last block of code can be written like this in the next release:


## If we wanted to, say, handle mouse events to let you drag the window or close it ...
Register-BootsEvent $clock -Event MouseLeftButtonDown -Action {
   $_.Handled = $true
   $this.DragMove() # WPF Magic!
}
Register-BootsEvent $clock -Event MouseRightButtonDown -Action {
   $_.Handled = $true
   $timer.Stop()  # we'd like to stop that timer now, thanks.
   $this.Close() # and close the windows
}
 

Similar Posts:

2 thoughts on “PowerBoots: Loading XAML Windows in PowerShell 1.0 or 2.0”

  1. Where is new-bootswindow? Is it in the DLL? I get “The term ‘New-BootsWindow’ is not recognized as a cmdlet, function, operable program, or script file” etc. I’m guessing the DLL should be loaded from the modules metafile (I see it in the “nestedModules” line in the psd1 file) – but I guess it’s quietly failing.

    Also – same error on Invoke-BootsWindow.

    I’ve tried this on PowerShellPlus and ISE

    Thanks!
    Chris

Comments are closed.