我正在用PowerShell SDK执行一个脚本,它利用了所有不同的流(信息、警告、详细信息等)。我可以正确地捕获它们的输出,但不能按它们生成的顺序捕获。下面是一个控制台应用程序(c#, .NET 7,安装了NuGet包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}");
}
正如你所看到的,我在不同的流上返回不同的结果。当然,当我像这样输出它们时,它们不再是正确的顺序:
[N]: Line 2
[V]: Line 1
[V]: Line 3
[V]: Line 6
[I]: Line 4
[I]: Line 5
我一直在看instance.Streams.Information
,instance.Streams.Verbose
等提供的对象-但我找不到一个属性,可以让我对它们进行排序。有趣的是,instance.Streams.Information
有一个TimeGenerated
,但它在所有其他流对象中都缺失了!
所以我被难住了,我怎么能做到这一点,有可能根据它们生成的时间对它们进行排序吗?
您需要预先订阅每个流集合上的相关事件,而不是等到执行完成。
为此,在每个相关的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();
}
现在我们只需要定义和实现ConsumeStreamOutput
方法来处理事件。它需要根据写入的流期望不同类型的输出。
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;
}
}
上面的输出应该是这样的:
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'
当然,如果您需要在其他地方处理数据,您可以将此方法更改为仅将数据收集到单个队列或类似的队列。
在阅读了更多关于PS主机的内容后,我找到了这样做的方法!基本上,可以使用以下行将所有其他流重定向到标准输出流:
instance.Commands.Commands[0].MergeMyResults(PipelineResultTypes.All,PipelineResultTypes.Output);
这样,instance.Invoke()
返回所有内容,包括详细信息流等。
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;
}
}
现在输出的顺序是正确的:
[VER]: Line 1
[OUT]: Line 2
[ERR]: Line 3
[VER]: Line 4
[INF]: Line 5
[INF]: Line 6
[VER]: Line 7