Powershell: Add_OutputDataReceived Event Causes Unhandled Error in PowerShell Process

Created on 16 Apr 2020  路  5Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

$tmr_OnTick = {
    param([System.Object] $sender, [System.Timers.ElapsedEventArgs] $eventArgs)
    "The Elapsed event was raised at {0:HH:mm:ss.fff}" -f $eventArgs.SignalTime | Write-Host;
}

$tmr = New-Object System.Timers.Timer
$tmr.Interval = 1000;
$tmr.Enabled = $true;
Register-ObjectEvent -InputObject $tmr -EventName Elapsed -Action $tmr_OnTick

$psStartInfo = New-Object System.Diagnostics.ProcessStartInfo -ArgumentList @((Get-Command -Name 'powershell').Path);
$psStartInfo.Arguments = '-Command "Start-Sleep 5; &whoami; exit"';
$psStartInfo.CreateNoWindow = $true;
$psStartInfo.UseShellExecute = $false;
$psStartInfo.RedirectStandardOutput = $true;
$psStartInfo.StandardOutputEncoding = [System.Text.Encoding]::ASCII
$posh = New-Object System.Diagnostics.Process
$posh.StartInfo = $psStartInfo;

$oStdOutBuilder = New-Object System.Text.StringBuilder;
$oStdOutEvent = {
    param([System.Object] $sender, [System.Diagnostics.DataReceivedEventArgs] $eventArgs)
    ($global:oStdOutBuilder).Append($eventArgs.Data);
}

# This works:
#Register-ObjectEvent -InputObject $posh -EventName 'OutputDataReceived' -Action $oStdOutEvent
# This doesn't:
$posh.Add_OutputDataReceived($oStdOutEvent);

"Everything is still working for now..." | Write-Host
for($i = 0; $i -lt 3; $i++) {
    Start-Sleep 1
}
"But after the child process spawns, it writes to stdout after 5 seconds..." | Write-Host

[void] $posh.Start();
$posh.BeginOutputReadLine();
"Let's just watch the events..." | Write-Host
for($i = 0; $i -lt 10; $i++) {
    Start-Sleep 1
}
[void] $posh.WaitForExit();

"Output Stream:`n$($oStdOutBuilder.ToString())" | Write-Host
$tmr.Dispose();

Expected behavior

mypcname\myusername

Actual behavior

An error has occurred that was not properly handled. Additional information is shown below. The PowerShell process will exit.
Unhandled exception. System.Management.Automation.PSInvalidOperationException: There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspace type. The script block you attempted to invoke was:
    param([System..($eventArgs.Data);

   at System.Management.Automation.ScriptBlock.GetContextFromTLS()
   at System.Management.Automation.ScriptBlock.InvokeAsDelegateHelper(Object dollarUnder, Object dollarThis, Object[] args)
   at lambda_method(Closure , Object , DataReceivedEventArgs )
   at System.Diagnostics.Process.OutputReadNotifyUser(String data)
   at System.Diagnostics.AsyncStreamReader.FlushMessageQueue(Boolean rethrowInNewThread)
--- End of stack trace from previous location where exception was thrown ---
   at System.Diagnostics.AsyncStreamReader.<>c.<FlushMessageQueue>b__18_0(Object edi)
   at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi)
   at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action`1 callback, TState& state)
   at System.Threading.QueueUserWorkItemCallback.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

Environment data

This occurs on all versions of PowerShell I have tried, I initially thought it was limited to 5.1 but it also happens all the way through to the latest. I originally reported the error here:
https://social.technet.microsoft.com/Forums/scriptcenter/en-US/e7e2ce89-79aa-4854-837b-9ff939c5c636/powershell-bug-report-addoutputdatareceived-events-causing-crashes

Name                           Value
----                           -----
PSVersion                      5.1.18362.752
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.752
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Name                           Value
----                           -----
PSVersion                      7.0.0
PSEdition                      Core
GitCommitId                    7.0.0
OS                             Microsoft Windows 10.0.18362
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Please let the 5.1 team(s) know, too. I expect my workplace will be more likely to run Windows Update than to install PS Core.

Issue-Question Resolution-Answered

Most helpful comment

Thanks @SeeminglyScience, your example makes it more clear where the behaviour originates.

@iSazonov I have tested both my example & the one given by @SeeminglyScience on the latest preview build and it behaves the same. Register-ObjectEvent is robust enough for my situation, however.

Apologies for the confusion. Had I tried it in >PS5.1 I would have found better keywords. I will close this ticket as I believe this to be related to the below items and is understood:
https://github.com/PowerShell/PowerShell/issues/11658
https://github.com/dotnet/runtime/issues/2024

All 5 comments

If an event is raised on a thread other than the default runspace thread then that's expected. That is (I assume) a large part of why Register-ObjectEvent exists. Is there an issue you're running into that you believe using the add accessor directly would resolve?

I am surprised that this would fail when other Add_EventName methods work for objects like buttons and other controls for forms. If I understand your explanation, this is specifically related to the fact that the event source is derived from a child process, right?

At a glance there is no functional difference for me to use Register-ObjectEvent for my use case. It is purely stylistic preference.

If I understand your explanation, this is specifically related to the fact that the event source is derived from a child process, right?

It's dependent on what thread the event is raised on. For example take this class:

Add-Type -TypeDefinition '
    using System;
    using System.Threading;

    public class Test
    {
        public event EventHandler ThingHappened;

        public void TriggerThingHappened()
        {
            new Thread(() => this.ThingHappened?.Invoke(this, null)).Start();
        }
    }'

$test = [test]::new()
$test.add_ThingHappened({})
$test.TriggerThingHappened()

There's significantly more logic that goes into Register-ObjectEvent that makes it possible to reroute the raised event to the right thread. When you're using the add accessor directly, you're just creating a very thin wrapper around a script block that C# code knows how to invoke. It doesn't know anything about what context to invoke it in though, so sometimes it will work sometimes it won't depending on the implementation of the class you're utilizing.

Please let the 5.1 team(s) know, too.

Windows PowerShell is frozen and get only critical and security fixes. All new work is only for PowerShell 7+.

Please repeat your tests with latest PowerShell 7.1 preview build.

Thanks @SeeminglyScience, your example makes it more clear where the behaviour originates.

@iSazonov I have tested both my example & the one given by @SeeminglyScience on the latest preview build and it behaves the same. Register-ObjectEvent is robust enough for my situation, however.

Apologies for the confusion. Had I tried it in >PS5.1 I would have found better keywords. I will close this ticket as I believe this to be related to the below items and is understood:
https://github.com/PowerShell/PowerShell/issues/11658
https://github.com/dotnet/runtime/issues/2024

Was this page helpful?
0 / 5 - 0 ratings