PowerShell WinForms interactive Prototyping/Debugging

By Beej

Credits:

Motivation

  • PowerShell’s interactive nature lends it to be a handy way to experiment with .Net objects… including UI elements like WinForms/WPF…
  • Yet firing up UI’s classically take over the active thread to service the user interaction (e.g. mouse events etc)…
  • Without the extra gravy below, our otherwise handy interactive powershell locks up tight until we close down the Windows Forms application thus releasing the main thread back to the command line
  • The following gravy throws a Windows Form onto a background thread such that we can continue to manipulate the UI objects WHILE THEY’RE RUNNING, yay!

The Gravy

create RunSpaceWinForm.ps1 as such

function RunSpaceWinForm {
    param($frm)
    if (!$frm) {return}

    #RunSpace is a PowerShell thread
    [System.Management.Automation.Runspaces.Runspace]$rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
    $rs.ApartmentState = "STA"
    $rs.ThreadOptions = "ReuseThread"
    $rs.Open()

    # make the WinForm object instantiated in current scope also available inside the runspace
    $rs.SessionStateProxy.SetVariable("frm", $frm)

    $ps = [System.Management.Automation.PowerShell]::Create()
    $ps.Runspace = $rs
    $rs.SessionStateProxy.SetVariable("ps", $ps)

    [System.IAsyncResult]$invokeHandle = $null
    $rs.SessionStateProxy.SetVariable("invokeHandle", $invokeHandle)

    # nugget: here's basically where the magic sauce kicks in
    # create the script that will run on the background thread, this lets WinForm have it's WndProc message pump while freeing our current PowerShell thread to further manipulate WinForm objects
    $ps.AddScript({
      #this call will take over the thread until the application is shut down by closing the main form
      [System.Windows.Forms.Application]::Run($frm)

      # clean up the powershell thread objects
      $ps.Runspace.Close()

      #honestly not sure if these commands work and are beneficial
      $ps.EndInvoke($invokeHandle)
      $ps.Runspace.Dispose() #this will block the runspace state on "closing" until you close the interactive powershell window
      $ps.Dispose()
    }) | Out-Null
    $invokeHandle = $ps.BeginInvoke()
    return "Use Debug-Runspace -id $($rs.Id) to activate breakpoints"
}

and here is simple usage… spin up a running WinForm and give control back to the powershell prompt

Add-Type -AssemblyName System.Windows.Forms
$frmMain = New-Object System.Windows.Forms.Form

. "{put_your_path_here}\RunSpaceWinForm.ps1"
RunSpaceWinForm $frmMain

Breakpoints

  • by default, breakpoints will now message as: “WARNING: Breakpoint Line breakpoint on ‘xyz’ will not be hit”
  • the above run will kick out something like the following…

    Use “Debug-Runspace -id 4” to go into breakpoint mode

  • execute that debug-runspace command and your breakpoints will light up in the editor and break when the code fires
  • CTRL-BREAK your way out of that mode when you want to go back to the powershell command line and manipulate the UI objects some more
Tags: PowerShell
Share: Twitter Facebook LinkedIn