简体   繁体   English

使用 PowerShell SDK 以正确的顺序捕获所有流

[英]Capturing all streams in correct sequence with PowerShell SDK

I'm executing a script with the PowerShell SDK, which makes use of all different streams (information, warning, verbose, ..).我正在使用 PowerShell SDK 执行一个脚本,它使用了所有不同的流(信息、警告、详细...)。 I can capture the output from them correctly, but not in the sequence they are generated.我可以正确地从它们中捕获 output,但不是按照它们生成的顺序。 As an example, here is a console app (C#, .NET 7, installed the NuGet package Microsoft.PowerShell.SDK ):例如,这是一个控制台应用程序(C#,.NET 7,安装了 NuGet package Microsoft.PowerShell.SDK ):

using System.Management.Automation.Runspaces;

var runSpace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault());
runSpace.Open();

var instance = System.Management.Automation.PowerShell.Create(runSpace);

instance.AddScript("""
    $VerbosePreference = 'Continue'

    Write-Verbose "Line 1"
    Write-Output "Line 2"
    Write-Verbose "Line 3"
    Write-Information "Line 4"
    Write-Information "Line 5"
    Write-Verbose "Line 6"
    """
);

var output = instance.Invoke();

foreach (var o in output)
{
    Console.WriteLine($"[N]: {o}");
}

foreach (var v in instance.Streams.Verbose)
{
    Console.WriteLine($"[V]: {v}");
}

foreach (var i in instance.Streams.Information)
{
    Console.WriteLine($"[I]: {i}");
}

As you can see I'm returning different results on different streams.如您所见,我在不同的流上返回不同的结果。 When I output them like that of course, they are no longer in the correct order:当我 output 他们当然是那样的时候,他们不再是正确的顺序:

[N]: Line 2
[V]: Line 1
[V]: Line 3
[V]: Line 6
[I]: Line 4
[I]: Line 5

I have been looking at the objects provided by instance.Streams.Information , instance.Streams.Verbose , etc. - but I could not find a property that would let me sort them.我一直在查看instance.Streams.Informationinstance.Streams.Verbose等提供的对象 - 但我找不到可以让我对它们进行排序的属性。 Interestingly instance.Streams.Information has a TimeGenerated , but it is missing from all the other stream objects!有趣的是instance.Streams.Information有一个TimeGenerated ,但所有其他 stream 对象都没有!

So I'm stumped how I could accomplish this, would it be possible to get these sorted based on the time they were generated?所以我很困惑如何才能做到这一点,是否有可能根据它们的生成时间对它们进行排序?

You'll want to subscribe to the relevant event on each stream collection up front, rather then waiting until execution has finished.您需要预先订阅每个 stream 集合的相关事件,而不是等到执行完成。

To do so, register handlers for the DataAdded event on each relevant instance.Streams.* property:为此,请在每个相关的instance.Streams.*属性上为DataAdded事件注册处理程序:

var runSpace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault());
runSpace.Open();

using (var instance = System.Management.Automation.PowerShell.Create(runSpace))
{
    instance.AddScript("""
        $VerbosePreference = 'Continue'

        Write-Verbose "Line 1"
        Write-Output "Line 2"
        Write-Verbose "Line 3"
        Write-Information "Line 4"
        Write-Information "Line 5"
        Write-Verbose "Line 6"
        """
    );

    # register event handler for the DataAdded event on each relevant stream collection
    instance.Streams.Verbose.DataAdded += ConsumeStreamOutput;
    instance.Streams.Information.DataAdded += ConsumeStreamOutput;

    # now create a data collection for standard output
    var outputCollection = new PSDataCollection<PSObject>();
    # ... and register the event handler on that too
    outputCollection.DataAdded += ConsumeStreamOutput;

    # ... and now we're finally ready to invoke the script
    instance.Invoke();
}

Now we just need to define and implement the ConsumeStreamOutput method that will handle the events.现在我们只需要定义和实现将处理事件的ConsumeStreamOutput方法。 It needs to expect different types of output based on stream it's written to.它需要根据写入的 stream 来预期不同类型的 output。

static void ConsumeStreamOutput(object sender, DataAddedEventArgs evtArgs)
{
    var data = ((System.Collections.IList)sender)[evtArgs.Index];

    switch (data)
    {
        case VerboseRecord vr:
            WriteLine($"Received Verbose stream data     :  '{vr}'");
            break;
        case InformationRecord ir:
            WriteLine($"Received Information stream data :  '{ir}'");
            break;
        default:
            WriteLine($"Received standard output         :  '{data}'");
            break;
    }
}

The above should give you an output like:上面应该给你一个 output 像:

Received Verbose stream data     :  'Line 1'
Received standard output         :  'Line 2'
Received Verbose stream data     :  'Line 3'
Received Information stream data :  'Line 4'
Received Information stream data :  'Line 5'
Received Verbose stream data     :  'Line 6'

Of course you can change this method to just collect the data to a single queue or similar if you need to process the data elsewhere.当然,如果您需要在其他地方处理数据,您可以更改此方法以仅将数据收集到单个队列或类似队列。

After reading some more about PS hosts, I found the way to do it: Basically it's possible to redirect all other streams to the standard output stream with this line:在阅读了更多关于 PS 主机的信息后,我找到了实现它的方法:基本上可以使用以下行将所有其他流重定向到标准 output stream:

instance.Commands.Commands[0].MergeMyResults(PipelineResultTypes.All,PipelineResultTypes.Output);

With that, instance.Invoke() returns everything, including verbose, information, etc. streams.这样, instance.Invoke()返回所有内容,包括详细信息、信息等流。

Then by checking what each PSObject is, I can do treat the objects differently depending on their type.然后通过检查每个PSObject是什么,我可以根据对象的类型对它们进行不同的处理。 Full sample:完整示例:

using System.Management.Automation;
using System.Management.Automation.Runspaces;

var runSpace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault());
runSpace.Open();

var instance = PowerShell.Create(runSpace);

instance.AddScript("""
    $VerbosePreference = 'Continue'

    Write-Verbose "Line 1"
    Write-Output "Line 2"
    Write-Error "Line 3"
    Write-Verbose "Line 4"
    Write-Information "Line 5"
    Write-Information "Line 6"
    Write-Verbose "Line 7"
    """
);

instance.Commands.Commands[0].MergeMyResults(PipelineResultTypes.All,PipelineResultTypes.Output);

var output = instance.Invoke();

foreach (var o in output)
{
    switch (o.ImmediateBaseObject)
    {
        case ErrorRecord er:
            Console.WriteLine($"[ERR]: {er}");
            break;
        case WarningRecord wr:
            Console.WriteLine($"[WRN]: {wr}");
            break;
        case VerboseRecord vr:
            Console.WriteLine($"[VER]: {vr}");
            break;
        case InformationRecord ir:
            Console.WriteLine($"[INF]: {ir}");
            break;
        case DebugRecord dr:
            Console.WriteLine($"[DBG]: {dr}");
            break;
        default:
            // I assume everything that ends up here is uncaptured output.
            Console.WriteLine($"[OUT]: {o}");
            break;
    }
}

And the output is now in the correct order: output 现在的顺序是正确的:

[VER]: Line 1
[OUT]: Line 2
[ERR]: Line 3
[VER]: Line 4
[INF]: Line 5
[INF]: Line 6
[VER]: Line 7

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM