PowerShell: Job Event Action with Form not executed

There are a lot of (StackOverflow) questions and answers about this ‘enduring mystique’ of combining form (or WPF) events with .NET events (like EngineEvents, ObjectEvents and WmiEvents) in PowerShell:

They are all come down two one point: even there are multiple threads setup, there are two different ‘listeners’ in one thread. When your script is ready to receive form events (using ShowDialog or DoEvents) it can’t listen to .NET events at the same time. And visa versa: if script is open for .NET events while processing commands (like Start-Sleep or specifically listen for .NET events using commands like Wait-Event or Wait-Job), your form will not be able to listen to form events. Meaning that either the .NET events or the form events are being queued simply because your form is in the same thread as the .NET listener(s) your trying to create.
As with the nimizen example, with looks to be correct at the first glans, your form will be irresponsive to all other form events (button clicks) at the moment you’re checking the backgroundworker’s state and you have to click the button over and over again to find out whether it is still ‘*Doing Stuff’. To work around this, you might consider to combine the DoEvents method in a loop while you continuously checking the backgroundworker’s state but that doesn’t look to be a good way either, see: Use of Application.DoEvents()
So the only way out (I see) is to have one thread to trigger the form in the other thread which I think can only be done with using [runspacefactory]::CreateRunspace() as it is able to synchronize a form control between the treats and with that directly trigger a form event (as e.g. TextChanged).

(if there in another way, I eager to learn how and see a working example.)

Form example:

Function Start-Worker {
    $SyncHash = [hashtable]::Synchronized(@{TextBox = $TextBox})
    $Runspace = [runspacefactory]::CreateRunspace()
    $Runspace.ThreadOptions = "UseNewThread"                    # Also Consider: ReuseThread  
    $Runspace.Open()
    $Runspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)          
    $Worker = [PowerShell]::Create().AddScript({
        $ThreadID = [appdomain]::GetCurrentThreadId()
        $SyncHash.TextBox.Text = "Thread $ThreadID has started"
        for($Progress = 0; $Progress -le 100; $Progress += 10) {
            $SyncHash.TextBox.Text = "Thread $ThreadID at $Progress%"
            Start-Sleep 1                                       # Some background work
        }
        $SyncHash.TextBox.Text = "Thread $ThreadID has finnished"
    })
    $Worker.Runspace = $Runspace
    $Worker.BeginInvoke()
}

[Void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$Form = New-Object Windows.Forms.Form
$TextBox = New-Object Windows.Forms.TextBox
$TextBox.Visible = $False
$TextBox.Add_TextChanged({Write-Host $TextBox.Text})
$Form.Controls.Add($TextBox)
$Button = New-Object System.Windows.Forms.Button
$Button.Text = "Start worker"
$Button.Add_Click({Start-Worker})
$Form.Controls.Add($Button)
$Form.ShowDialog()

For a WPF example, see: Write PowerShell Output (as it happens) to WPF UI Control

Leave a Comment