Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powershell: Updating GUI from background job results

EDIT : I was able to get it working, see below for my solution. The commenters below are correct that Powershell isn't really ideal for GUI's and threading, but it can be done.


I've got a form in Powershell that uses Start-Job to run functions in the background without freezing the GUI. My goal is to continuously check the status of those jobs for their output. I managed to use the Windows Forms Timer to check the results of the job and update the GUI accordingly.

It's all working fine, but it seems sloppy. Is this the best way to accomplish a GUI refresh? I'm relatively new to Powershell and I want to improve my coding.

Example of what I'm doing:

$jobScript = {
    Start-Sleep 5
    Write-Output "The job has finished running"
}

$timerScript = {
    $timer.Stop()
    $jobResult = Get-Job | Receive-Job -Keep
    if ($jobResult) {
        $btn.text = $jobResult
    } else {
        $timer.Start()
    }
}

Add-Type -AssemblyName System.Windows.Forms
$form             = New-Object System.Windows.Forms.Form
$form.ClientSize  = '300,300'
$form.topmost     = $true

$btn              = New-Object System.Windows.Forms.Button
$btn.Text         = "The job is still running"
$btn.Width        = 300
$btn.Height       = 300
$form.Controls.Add($btn)

$timer            = New-Object System.Windows.Forms.Timer
$timer.Interval   = 100
$timer.add_Tick($timerScript)
$timer.Start()

Start-Job -ScriptBlock $jobScript

$form.ShowDialog()

Update: My solution

Using Register-ObjectEvent did not work, it seemed like it was fighting with the GUI for the thread. Instead I was able to use [System.Windows.Forms.Application]::DoEvents(). This allows the GUI to be moved around, and once it's done being moved, the thread will resume. The big caveat here is that execution is paused as long as the GUI is being moved, so if your code needs to react to the background job on a time limit, this could cause errors.

Example code block:

$jobScript =
{
    Start-Sleep 5
    Write-Output "The job is completed"
}

Add-Type -AssemblyName System.Windows.Forms
$form             = New-Object System.Windows.Forms.Form
$form.ClientSize  = '300,300'
$form.topmost     = $true

$btn              = New-Object System.Windows.Forms.Button
$btn.Text         = "..."
$btn.Width        = 300
$btn.Height       = 300
$form.Controls.Add($btn)

$btn.add_click({
    $btn.Text = "Starting job"
    $jobby = Start-Job -ScriptBlock $jobScript
    Do {[System.Windows.Forms.Application]::DoEvents()} Until ($jobby.State -eq "Completed")
    $btn.Text = Get-Job | Receive-Job
})

$form.ShowDialog()
like image 730
Luke DeWitt Avatar asked Dec 10 '18 22:12

Luke DeWitt


1 Answers

You can use events:

 $job = Start-Job {sleep 3; Write-Output "Dummy job completed"}

$callback = {

Write-Host "Event Fired"
Unregister-Event -SourceIdentifier "DummyJob"
Write-Host ($job | Receive-Job)

}

$evt = Register-ObjectEvent -InputObject $job -EventName StateChanged -SourceIdentifier "DummyJob" -Action $callback 


# to remove all 
Get-Job | Remove-Job -Force # jobs and events
Get-EventSubscriber | Unregister-Event  # events
like image 106
Mike Twc Avatar answered Sep 29 '22 05:09

Mike Twc