Long-running background tasks in ShowUI GUIs from PowerShell

A lot of the time when you’re writing ShowUI user interfaces in PowerShell, you’re just asking users for inputs, prompting them for choices, or showing them the results of some calculations (whether that be dashboards, graphics, etc).

However, sometimes you need to prompt the user for input and then do some work, and you want to present the users with output as the work progresses. With any graphical user interface, when you want to do any significant amount of work, you need to do it on a background thread. The ShowUI way to do that is to use the Invoke-Background command, which gives you an event-driven way to run a script block (or command) and capture all of it’s various outputs.

I’ll show you an example here which writes output and progress events, and show you how to handle updating the user interface with both (hopefully you’ll be able to figure out the other output options, including the errors).


Import-Module ShowUI -Req 1.4

New-Grid -Columns "*","5","70" -Rows "21","Auto","5","*" -Margin 5 {
  ProgressBar -Name ProgressBar -ColumnSpan 3 -Maximum 100 -Margin "0,0,0,5"

  TextBox -Name count -Text 12 -MinWidth 150 -Row 1

  TextBox -Name output -MinHeight 100 -ColumnSpan 3 -IsReadOnly -Row 3

  Button "Start" -Name StartButton -Column 2 -Row 1 -On_Click {
    # If you need to pass values from your form to the background function
    # They need to be serializable, and go through the -Parameter hashtable:
    Invoke-Background -Parameter @{ work = [int]$count.Text } {
      param($work=10)
      # You would do real work here, but I'll just fake it
      foreach($item in 1..$work) {
        # Anything that goes to Output triggers the OutputChanged
        Write-Output "Processing item $item of $work"
        Start-Sleep -milli 500
        # And of course, progress triggers the ProgressChanged
        $progress = (([double]$item / [double]$work) * 100)
        Write-Progress "Processing" -PercentComplete $progress
      }
    } -On_OutputChanged {
      # The actual event is on the DataContext object
      $output.Text = $args[0].DataContext.Output -join "`n" | Out-String
    } -On_ProgressChanged {
      $ProgressBar.Value = $args[0].DataContext.LastProgress.PercentComplete
    }
  }
} -Show
 

The first key, of course, is knowing that Invoke-Background sets a DataContext on the object (either the object specified to the -Control parameter, or the parent who’s event handler calls it). That DataContext is a ShowUI.PowerShellDataSource (it’s code is in the C# folder in the ShowUI module), and it has events and properties for each of the streams: Output, Error, Warning, Verbose, Debug, and Progress. It also has a property for the “Last” item from each of those streams, and a TimeStampedOutput property which collects all of the output, in order, with a NoteProperty for the Stream and the TimeStamp. More importantly, it has events which fire on every output.

The second key is knowing that the properties like “Output” contain all the output, and properties like “LastOutput” contain only the very last thing output. Since in the case of the progress we only care about the very last value, we’re able to use the “LastProgress” property. Since we want to show all of the output, rather than try to collect the “last” one each time, we can just overwrite the textbox’s text with the output array.

It’s also very important to remember that the properties are arrays, and in particular to remember that the Output is an array of PSObject, and the others are arrays of ProgressRecords, DebugRecords, ErrorRecords, etc… so we use Out-String to make sure that we convert them to a string before we set the Text of our output pane.

Hopefully this function will help you out — I’ll try to improve it’s help and examples in the next release of ShowUI.

Similar Posts:

One thought on “Long-running background tasks in ShowUI GUIs from PowerShell”

Comments are closed.