Powershell: Is there a way to capture interleaved output and PSDataStreams from a PowerShell instance?

Created on 8 Aug 2018  路  10Comments  路  Source: PowerShell/PowerShell

The output and PSDataStreams are available individually from a PowerShell instance. These individual streams, however, do not seem to contain enough information to determine order of an item in one stream compared with an item in another stream.

Steps to reproduce

$ErrorActionPreference = 'Continue'
$powershell = [powershell]::Create().AddScript({
    $VerbosePreference = 'Continue'
    'output1'
    Write-Verbose 'verbose1'
    Write-Error 'error1'
    Write-Error 'error2'
    Write-Verbose 'verbose2'
    'output2'
})

$powershell.Invoke()
$powershell.Streams.Verbose
$powershell.Streams.Error

Behavior I'm looking for

output1
verbose1
    $VerbosePreference = 'Continue'
    'output1'
    Write-Verbose 'verbose1'
    Write-Error 'error1'
    Write-Error 'error2'
    Write-Verbose 'verbose2'
    'output2'
 : error1
+ CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException

    $VerbosePreference = 'Continue'
    'output1'
    Write-Verbose 'verbose1'
    Write-Error 'error1'
    Write-Error 'error2'
    Write-Verbose 'verbose2'
    'output2'
 : error2
+ CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
verbose2
output2

Actual behavior

output1
output2
verbose1
verbose2
    $VerbosePreference = 'Continue'
    'output1'
    Write-Verbose 'verbose1'
    Write-Error 'error1'
    Write-Error 'error2'
    Write-Verbose 'verbose2'
    'output2'
 : error1
+ CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException

    $VerbosePreference = 'Continue'
    'output1'
    Write-Verbose 'verbose1'
    Write-Error 'error1'
    Write-Error 'error2'
    Write-Verbose 'verbose2'
    'output2'
 : error2
+ CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.1.0-preview.4
PSEdition                      Core
GitCommitId                    6.1.0-preview.4
OS                             Microsoft Windows 6.3.9600 
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Question Resolution-Answered WG-Engine

All 10 comments

Seems we discussed this before /cc @mklement0

There isn't a way to correlate the streams but you can use the events on the various streams to insert elements into an explicit single output stream in real time. It's a bit clunky but the code to do this would look something like:.


$p = [powershell]::Create().AddScript{
            "Output"        
            $ErrorActionPreference = "continue"
            Write-Error "Error"
            Write-Verbose -Verbose "Verbose"
            "Output2"        
            $DebugPreference = "continue"
            Write-Debug "Debug"
            "Output3"        
            $InformationPreference="continue"
            Write-Information "Information"
    }

$output = [System.Management.Automation.PSDataCollection[object]]::new()

$p.Streams.Error.add_DataAdded{
    $p.Streams.Error.ReadAll().foreach{$output.Add($_)}
}
$p.Streams.Verbose.add_DataAdded{
    $p.Streams.Verbose.ReadAll().foreach{$output.Add($_)}
}
$p.Streams.Debug.add_DataAdded{
    $p.Streams.Debug.ReadAll().foreach{$output.Add($_)}
}
$p.Streams.Information.add_DataAdded{
    $p.Streams.Information.ReadAll().foreach{$output.Add($_)}
}

$p.Invoke($null, $output)

foreach ($item in $output)
{
    "output: $item"
} 

This will give you a single stream containing all of the records in the order they were written which you can then demultiplex based on type (ErrorRecord, DebugRecord, VerboseRecord, etc.)

@BrucePay That seems pretty good for my purposes. Can I rely on the .DataAdded event handlers to be called in the order that matches the output from the scriptblock?

@alx9r Yes.

I'm trying to do the same thing as above in C# with the .NET Framework using this package (https://www.nuget.org/packages/Microsoft.PowerShell.5.ReferenceAssemblies/). I can't seem to access add_DataAdded from the PowerShell streams (after creating a PowerShell object using PowerShell.Create()).

Do you know how I can use this? I'm not sure if add_DataAdded is only available in what looks like PowerShell or if maybe there's a version difference or something. Thanks.

@DrDinosaur I'm pretty sure I have been using .DataAdded += myEventHandler in C# rather than .add_DataAdded(myEventHandler).

Yeah, I eventually figured that out. I didn't understand PowerShell was trying to make an event handler with that syntax.

Yeah, I eventually figured that out. I didn't understand PowerShell was trying to make an event handler with that syntax.

How?驴

Thanks.

Yeah, I eventually figured that out. I didn't understand PowerShell was trying to make an event handler with that syntax.

How?驴

Thanks.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events

Yeah, I eventually figured that out. I didn't understand PowerShell was trying to make an event handler with that syntax.

How?驴
Thanks.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events

How to: Subscribe to and Unsubscribe from Events - C# Programming Guide

No way?, only error output events are fired.

Already tried:
`
s_ps.Streams.Verbose.DataAdding += delegate (object sender, DataAddingEventArgs e) {
var records = (PSDataCollection)sender;
Debug.WriteLine("Progress verbose contains {0} records", records.Count);
};

        s_ps.Streams.Progress.DataAdding += delegate (object sender, DataAddingEventArgs e) {
            var records = (PSDataCollection<ProgressRecord>)sender;
            Debug.WriteLine("Progress verbose contains {0} records", records.Count);
        };

        s_ps.Streams.Verbose.DataAdding += delegate (object sender, DataAddingEventArgs e) {
            var records = (PSDataCollection<VerboseRecord>)sender;
            Debug.WriteLine("Progress verbose contains {0} records", records.Count);
        };

        s_ps.Streams.Debug.DataAdding += delegate (object sender, DataAddingEventArgs e) {
            var records = (PSDataCollection<DebugRecord>)sender;
            Debug.WriteLine("Progress debug contains {0} records", records.Count);
        };

        s_ps.Streams.Error.DataAdding += delegate (object sender, DataAddingEventArgs e) {
            var records = (PSDataCollection<ErrorRecord>)sender;
            Debug.WriteLine("Progress error contains {0} records", records.Count);
        };
        s_ps.Streams.Warning.DataAdding += delegate (object sender, DataAddingEventArgs e) {
            var records = (PSDataCollection<WarningRecord>)sender;
            Debug.WriteLine("Progress warning contains {0} records", records.Count);
        };

Also tried creating runspace events:
pipaLinia = s_ps.Runspace.CreatePipeline();

pipaLinia.Output.DataReady += new EventHandler(Output_DataReady);

pipaLinia.Error.DataReady += new EventHandler(Error_DataReady);

`

I am lost :(

Was this page helpful?
0 / 5 - 0 ratings