My PowerPoint slides from PowerShell Virtual User Group #2

In case anyone is interested, here are my slides in PowerPoint PPTX format from the most recent PS Virtual User Group. It covers the new Path handling infrastructure for PscxCmdlets in the upcoming PowerShell Community Extensions 1.2 and some brief information on my PowerShell Eventing snap-in for PowerShell.

psvug2_oisin_grehan.zip (152 KB)

(updated to a zip: it appears DasBlog will not serve pptx files?)

Why AppDomains are not a Magic Bullet

As knowledge of PowerShell increases for those new to .NET, there comes a point when people start to notice some shortcomings of the Assembly loading/unloading mechanisms of the 2.0 CLR. Namely, once you load an assembly into PowerShell to use it, you can't unload it again. The only way to remove it from memory is to restart PowerShell. Eventually, you might read something about how Assemblies can be loaded into AppDomains, and AppDomains themselves can be unloaded. This is true, but for the most part it is not much use in PowerShell unless the Types in question where specifically designed with this in mind. For those of you who understand enough of what I'm talking about to get this far without going "huh?", the following script will demonstrate some of the issues at hand:

Before you run this script, please disable PowerTab or any other SnapIns that may load the WinForms assembly into the current AppDomain. In short, this script creates a Form object in a child AppDomain, examines the current AppDomain for the WinForms assembly. It then attempts to manipulate the Form and again examines the current AppDomain for the WinForms assembly.

  1. # full qualified display name to WinForms assembly   
  2. $assembly = "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  
  3.   
  4. function IsWinFormsLoaded() {   
  5.     $loaded = [appdomain]::currentdomain.getassemblies()   
  6.     $winforms = $loaded | ? { $_.fullname -like "system.windows*" }   
  7.     return ($winforms -ne $null)       
  8. }   
  9.   
  10. if (-not (IsWinFormsLoaded)) {   
  11.     "Creating child AppDomain..."  
  12.     $child = [appdomain]::Createdomain("child",$null,$null)   
  13.   
  14.     # create a remote instance of a WinForms Form in a child AppDomain   
  15.     "Creating remote WinForms Form in child AppDomain... "  
  16.     $handle = $child.CreateInstance($assembly"System.Windows.Forms.Form")   
  17.   
  18.     # examine returned ObjectHandle   
  19.     "Returned object is a {0}" -f $handle.GetType()   
  20.     $handle | gm # dump methods   
  21.   
  22.     # Did WinForms get pulled into our AppDomain?   
  23.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  24.   
  25.     # attempt to manipulate remote object, so unwrap   
  26.     "Unwrapping, examining methods..."  
  27.     $form = $handle.Unwrap()   
  28.     $form | gm | select -first 10   
  29.   
  30.     # is Windows Forms loaded now?   
  31.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  32.   
  33. else {   
  34.     write-warning "System.Windows.Forms is already loaded. Please disable PowerTab or other SnapIns that may load System.Windows.Forms and restart PowerShell."  
  35. }  

Hopefully this will clear up any outstanding questions. I'll post more information about this later, or possibly add to this post.

appdomains.ps1 (1.33 KB)

Foreground/Background Swappable Downloads in PowerShell

Here's another interesting use for my PowerShell Eventing Snap-In, where I'm simulating unix-style foreground/background tasks. In this case, the task is a large download using System.Net.WebClient. Just dot-source the script below and start a download using the Start-Download function, passing the url to the large file and the local path where to save your file (be sure to fully qualify the save path). The download will start immediately and show a progress bar with bytes remaining and a percentage, however you can hit Ctrl+C at any time and the download will continue in the background. You can get back to PowerShell tasks, and bring back up the progress bar by invoking Show-Progress at any time. Use Stop-Download to cancel the currently active download. Only one download can be active at a time, but this could easily be extended to support a pool of downloads (using multiple WebClient objects).

downloads.ps1 (1.85 KB)

  1. #requires -pssnapin pseventing  
  2.  
  3. function Stop-Download {  
  4.     $wc.CancelAsync()  
  5. }  
  6.  
  7. function Start-Download {  
  8.     param(  
  9.         [uri]$Url = $(throw "need Url."),  
  10.         [string]$Path = $(throw "Need save path.")  
  11.     )  
  12.  
  13.     # initialise webclient  
  14.     if (-not $global:wc) {  
  15.         $global:wc = New-Object System.Net.WebClient  
  16.         $var = get-variable wc  
  17.         Connect-EventListener -Variable $var `  
  18.             -EventName DownloadProgressChanged, DownloadFileCompleted         
  19.     }  
  20.  
  21.     if ($wc.IsBusy) {  
  22.         Write-Warning "Currently busy: Please cancel current download or wait until it is completed." 
  23.         return 
  24.     }  
  25.       
  26.     $wc.DownloadFileAsync($url, $path, $path) # last is userstate  
  27.     Write-Host "Download started. Ctrl+C to continue in background." 
  28.     Show-Progress  
  29. }  
  30.  
  31. function Show-Progress {  
  32.     if (-not $wc.IsBusy) {  
  33.         # possibly already completed, clear queue  
  34.         get-event | Out-Null 
  35.         "No active download." 
  36.         return 
  37.     }  
  38.       
  39.     Start-KeyHandler -CaptureCtrlC  
  40.       
  41.     trap [exception] {  
  42.         Stop-KeyHandler  
  43.         Write-Warning "error" 
  44.         $_ 
  45.         return 
  46.     }  
  47.       
  48.     $exiting = $false 
  49.       
  50.       
  51.     while (-not $exiting) {       
  52.         $events = @(Get-Event -Wait)          
  53.         $event = $null;  
  54.           
  55.         # skip to last event  
  56.         foreach ($event in $events) {  
  57.             if ($event.Name -eq "CtrlC") {  
  58.                 break;  
  59.             }  
  60.         }         
  61.           
  62.         switch ($event.Name) {  
  63.             "DownloadProgressChanged" {  
  64.                 $r = $event.args.BytesReceived  
  65.                 $t = $event.args.TotalBytesToReceive  
  66.                 $activity = "Downloading $($event.args.userstate)" 
  67.                 $p = [math]::Ceiling(([double]$r / $t) * 100)  
  68.                 Write-Progress -Activity $activity -PercentComplete $p `  
  69.                     -Status ("{0} of {1} byte(s)" -f $r,$t)  
  70.             }  
  71.             "DownloadFileCompleted" {  
  72.                 Write-Progress -Activity DownloadComplete -Completed  
  73.                 "Complete." 
  74.                 $exiting = $true 
  75.             }  
  76.             "CtrlC" {  
  77.                 "Switching to background downloading. Show-Progress to move to foreground." 
  78.                 $exiting = $true 
  79.             }  
  80.         }         
  81.     }  
  82.     Stop-KeyHandler  

PowerShell Patterns #2

Hmmm... that old typographical oddity "the quick brown fox jumps over the lazy dog," does it really use all letters from "A" to "Z?"

  1. [char]"a" .. [char]"z" | ? { "the quick brown fox jumps over the lazy dog".getenumerator() -contains $_ } | % { [char]$_

I guess it's true then.

Using Windows Forms Controls in PowerShell #1: ListBox

Using Windows Forms controls in PowerShell is a tricky thing, because it only supports very simple event listboxhandling. However, some time ago I wrote a Snap-In to allow anyone to work with .NET events, even asynchronous ones. You can download it from my PSEventing CodePlex project; it comes with full help via the normal PowerShell mechanisms of -? and get-help. Examples are also available online on how to use the project for more complex scenarios. Here's simple demonstration of autogenerating a listbox control filled with choices given as arguments to a simple script:

  1. # as simple as 1,2,3 :-)  
  2. $choice = .\get-choice.ps1 "one","two","three" 

And here is the source to the get-choice.ps1 script itself. This requires PSEventing 1.0 or 1.1 Beta.

  1. #requires -pssnapin pseventing  
  2.  
  3. # http://www.codeplex.com/pseventing (1.0 or 1.1)  
  4.  
  5. param([string[]]$choices = $(throw "please supply a string array of choices"))  
  6.  
  7. if ($choices.length -eq 0) {  
  8.     Write-Warning "cannot be a zero length array." 
  9.     return 
  10. }  
  11.  
  12. # initialize form  
  13. $form = new-object windows.forms.form  
  14. $form.Text = "Choose..." 
  15. $form.MinimizeBox = $false 
  16. $form.MaximizeBox = $false 
  17. $form.AutoSize = $true 
  18. $form.AutoSizeMode = "GrowAndShrink" 
  19.  
  20. # initialize listbox  
  21. $listbox = new-object windows.forms.listbox  
  22. $choices | % { [void]$listbox.items.add($_) }  
  23.  
  24. $form.controls.Add($listbox)  
  25.  
  26. # catch a choice in the listbox (remove -verbose for quiet mode)  
  27. Connect-EventListener -VariableName listbox -EventName SelectedIndexChanged -Verbose  
  28.  
  29. # catch someone closing the form (remove -verbose for quiet mode)  
  30. Connect-EventListener -VariableName form -EventName Closed -Verbose  
  31.  
  32. $form.Show()  
  33.  
  34. # wait for an event while performing sendmessage pumping (or ctrl+c to exit)  
  35. $event = Get-Event -Wait  
  36.  
  37. # don't pollute pipeline (remove in production)  
  38. write-host ($event | ft -auto | out-string)  
  39.  
  40. $form.Dispose()  
  41.  
  42. $choice = $listbox.SelectedItem  
  43.  
  44. # clean up; event listeners will be automatically unhooked ;-)  
  45. $form = $null 
  46. $listbox = $null 
  47.  
  48. # return chosen item, or $null if the form was closed  
  49. if ($event.Name -eq "SelectedIndexChanged") {  
  50.     $choice 
  51. } else {  
  52.     $null # cancelled  
Have fun!

PowerShell Patterns #1

It's very easy sometimes to look at the PowerShell grammar and partition it into two distinct styles: pipeline and traditional imperative script. In fact, it took a number of weeks of playing around before I realised that this is leaving out a large portion of patterns that employ a fusion of these two styles. For example, Let's iterate over an array of numbers, skipping null elements; first, using the pipeline style:

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. $arr | where-object { $_ -ne $null } | foreach-object { $_

Then, using a traditional VBScript style, using the foreach keyword, as opposed to the foreach-object cmdlet:

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. foreach ($elem in $arr) {  
  4.     if ($elem -ne $null) {  
  5.         $elem 
  6.     }  

And finally, a fusion of the two styles whereby I insert a pipeline into the expression, and also take advantage of the fact that $null will evaluate to $false, thereby skipping the need to test with the -eq operator (update: Keith Hill reminded me that both 0 and "" will also evaluate to $false, so I added an explicit -ne $null):

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. foreach ($elem in $arr | where-object {$_ -ne $null} ) {  
  4.     $elem 

As Mr. Snover is fond of saying, this is a great example of PowerShell's pithiness. PithyShell at its best!

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List