Skip to content

InvocationStateChanged never fires Completed after BeginInvoke() on custom runspace #27251

@RG255

Description

@RG255

Bug: InvocationStateChanged never fires Completed after BeginInvoke() on custom runspace

Summary

When a PowerShell instance is run via BeginInvoke() on a custom runspace created with
RunspaceFactory::CreateRunspace(), the InvocationStateChanged event fires once with state
Running when execution begins but never fires with Completed, Failed, or Stopped
after the script finishes — even for a trivially short script that exits cleanly.

Environment

  • PowerShell version: 7.6.0 (Core)
  • OS: Microsoft Windows 10.0.19045 (Win32NT)
  • Repro mode: Interactive pwsh.exe session (no WPF, no additional threads)

Steps to reproduce

Both variants below produce the same result. ThreadOptions does not affect the behaviour.

Variant A (with ReuseThread):

$Rs = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$Rs.ApartmentState = 'MTA'
$Rs.ThreadOptions  = 'ReuseThread'
$Rs.Open()

$Ps = [System.Management.Automation.PowerShell]::Create()
$Ps.Runspace = $Rs

$Ps.add_InvocationStateChanged({
    param($s, $e)
    Write-Host "State: $($e.InvocationStateInfo.State)"
})

$null = $Ps.AddScript('1..3 | ForEach-Object { Start-Sleep -Milliseconds 100 }; "done"')
$null = $Ps.BeginInvoke()

Start-Sleep -Seconds 2
Write-Host "Final PS state: $($Ps.InvocationStateInfo.State)"

Variant B (without ThreadOptions — default NewThread):

$Rs = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$Rs.ApartmentState = 'MTA'
$Rs.Open()

$Ps = [System.Management.Automation.PowerShell]::Create()
$Ps.Runspace = $Rs

$Ps.add_InvocationStateChanged({
    param($s, $e)
    Write-Host "State: $($e.InvocationStateInfo.State)"
})

$null = $Ps.AddScript('1..3 | ForEach-Object { Start-Sleep -Milliseconds 100 }; "done"')
$null = $Ps.BeginInvoke()

Start-Sleep -Seconds 2
Write-Host "Final PS state: $($Ps.InvocationStateInfo.State)"

Actual output (both variants)

State: Running
Final PS state: Running

Expected output

State: Running
State: Completed
Final PS state: Completed

The InvocationStateChanged event should fire a second time with state Completed once the
script finishes executing. It does not. Polling $Ps.InvocationStateInfo.State directly after
the script has had time to run also returns Running permanently. The behaviour is identical
regardless of ThreadOptions.

Impact

Any code that relies on InvocationStateChanged to detect script completion — for example,
re-enabling UI controls or disposing resources after a background task finishes — will never
receive the completion notification. The PowerShell instance and its Runspace cannot be
safely disposed, and there is no reliable built-in mechanism to know when execution has ended.

This pattern is commonly used in WPF/WinForms applications to run background PowerShell work
off the UI thread. The broken event makes that pattern unusable without a workaround.

Workaround

Have the script signal its own completion through an out-of-band mechanism — for example,
enqueueing a sentinel value into a ConcurrentQueue that is polled by a timer on the UI thread:

$Q = [System.Collections.Concurrent.ConcurrentQueue[String]]::new()
$Rs.SessionStateProxy.SetVariable('_Q', $Q)

$null = $Ps.AddScript('
    1..3 | ForEach-Object { $env:COMPUTERNAME | Out-Null }
    $_Q.Enqueue("--- Done ---")
')
$null = $Ps.BeginInvoke()

# Poll until done (or use a DispatcherTimer / background thread)
while ($true) {
    $item = ''
    if ($Q.TryDequeue([ref]$item) -and $item -eq '--- Done ---') { break }
    Start-Sleep -Milliseconds 100
}
Write-Host "Script finished (via workaround)"

This is an unreasonable burden on callers. The event should work.

Additional notes

  • $Ps.InvocationStateInfo.State never transitions from Running even after the script
    completes and the background thread has exited.
  • The issue reproduces with both ReuseThread and the default NewThreadThreadOptions
    is not a factor.
  • The issue was first noticed in a WPF GUI application running on PS 7.6.0 under Windows 10,
    but the minimal repro above confirms it is not WPF-specific — it reproduces in a plain
    interactive pwsh.exe session with no UI framework involved.
  • Using a RunspacePool instead of a single custom Runspace has not been tested and may
    or may not exhibit the same behaviour.
  • UseCurrentThread has not been tested.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions