Cmdlet 详细流
Cmdlet verbose stream
我目前正在使用 PowerShell 5.0 SDK 编写 C# cmdlet。
当 运行 来自 "real time".
中的 powershell 时,我正在尝试将第三方可执行文件的 StandardError 传递到 cmdlet 输出
我目前正在使用 MedallionShell 库来处理 运行 进程。我已经用普通的 C# win 表单尝试过这个,并使用 Command.StandardError.PipeToAsync(Console.OpenStandardOutput()) 来打印输出,因为可执行文件在 "real time" 中将它生成到控制台。
我尝试创建自己的调用 WriteVerbose 的 Stream 对象,但它似乎没有在 powershell 屏幕上打印任何内容(我在 运行 时将 -Verbose 传递给 cmdlet)。
我目前的流程是这样的:
- 打开 Powershell ISE
- 加载我的模块(C# dll)
- 使用参数调用我的 cmdlet
- Command.Run
- Command.StandardError.PipeToAsync(???)
- Command.Wait(在此步骤中,输出应流向 powershell window)
- 检查Command.Result.Success。
任何人都可以指出我正确的方向吗?
您不能只从任意线程调用 Cmdlet
的 Write
方法(如 WriteVerbose
)。您需要将对此方法的调用编组回管道线程。一种方法是实现消息循环,当其他线程想要调用管道线程中的某些内容时,它会处理来自其他线程的消息。
Add-Type @‘
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
[Cmdlet(VerbsLifecycle.Invoke, "Process")]
public class InvokeProcessCmdlet : Cmdlet {
[Parameter(Position = 1)]
public string FileName { get; set; }
[Parameter(Position = 2)]
public string Arguments { get; set; }
protected override void EndProcessing() {
using(BlockingCollection<Action> messageQueue = new BlockingCollection<Action>()) {
using(Process process = new Process {
StartInfo=new ProcessStartInfo(FileName, Arguments) {
UseShellExecute=false,
RedirectStandardOutput=true,
RedirectStandardError=true
},
EnableRaisingEvents=true
}) {
int numberOfCompleteRequests = 0;
Action complete = () => {
if(Interlocked.Increment(ref numberOfCompleteRequests)==3) {
messageQueue.CompleteAdding();
}
};
process.OutputDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteObject(args.Data));
}
};
process.ErrorDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteVerbose(args.Data));
}
};
process.Exited+=(sender, args) => complete();
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
foreach(Action action in messageQueue.GetConsumingEnumerable()) {
action();
}
}
}
}
}
’@ -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
你可以用这样的东西来测试它:
Invoke-Process icacls 'C:\* /c' -Verbose
如果您从 Cmdlet
的 PSCmdlet
instrad 派生,您将可以访问 this.Host.UI.WriteVerboseLine
,可以从任何线程调用它 风险自负(我认为它不会以任何方式防止输出的字符串以错误的方式混合)
无论如何,根据我的经验,到目前为止它一直运行良好,如果 cmdlet 是只有你会使用的东西,我认为风险是可以接受的。
同样,如果在控制台中使用它也能很好地工作,我不知道如果您稍后将详细流重定向到控制台以外的其他地方或没有 "UI"
如果您有更多时间来实施,@PetSerAl 解决方案肯定更合适
我目前正在使用 PowerShell 5.0 SDK 编写 C# cmdlet。
当 运行 来自 "real time".
中的 powershell 时,我正在尝试将第三方可执行文件的 StandardError 传递到 cmdlet 输出我目前正在使用 MedallionShell 库来处理 运行 进程。我已经用普通的 C# win 表单尝试过这个,并使用 Command.StandardError.PipeToAsync(Console.OpenStandardOutput()) 来打印输出,因为可执行文件在 "real time" 中将它生成到控制台。
我尝试创建自己的调用 WriteVerbose 的 Stream 对象,但它似乎没有在 powershell 屏幕上打印任何内容(我在 运行 时将 -Verbose 传递给 cmdlet)。
我目前的流程是这样的:
- 打开 Powershell ISE
- 加载我的模块(C# dll)
- 使用参数调用我的 cmdlet
- Command.Run
- Command.StandardError.PipeToAsync(???)
- Command.Wait(在此步骤中,输出应流向 powershell window)
- 检查Command.Result.Success。
任何人都可以指出我正确的方向吗?
您不能只从任意线程调用 Cmdlet
的 Write
方法(如 WriteVerbose
)。您需要将对此方法的调用编组回管道线程。一种方法是实现消息循环,当其他线程想要调用管道线程中的某些内容时,它会处理来自其他线程的消息。
Add-Type @‘
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
[Cmdlet(VerbsLifecycle.Invoke, "Process")]
public class InvokeProcessCmdlet : Cmdlet {
[Parameter(Position = 1)]
public string FileName { get; set; }
[Parameter(Position = 2)]
public string Arguments { get; set; }
protected override void EndProcessing() {
using(BlockingCollection<Action> messageQueue = new BlockingCollection<Action>()) {
using(Process process = new Process {
StartInfo=new ProcessStartInfo(FileName, Arguments) {
UseShellExecute=false,
RedirectStandardOutput=true,
RedirectStandardError=true
},
EnableRaisingEvents=true
}) {
int numberOfCompleteRequests = 0;
Action complete = () => {
if(Interlocked.Increment(ref numberOfCompleteRequests)==3) {
messageQueue.CompleteAdding();
}
};
process.OutputDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteObject(args.Data));
}
};
process.ErrorDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteVerbose(args.Data));
}
};
process.Exited+=(sender, args) => complete();
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
foreach(Action action in messageQueue.GetConsumingEnumerable()) {
action();
}
}
}
}
}
’@ -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
你可以用这样的东西来测试它:
Invoke-Process icacls 'C:\* /c' -Verbose
如果您从 Cmdlet
的 PSCmdlet
instrad 派生,您将可以访问 this.Host.UI.WriteVerboseLine
,可以从任何线程调用它 风险自负(我认为它不会以任何方式防止输出的字符串以错误的方式混合)
无论如何,根据我的经验,到目前为止它一直运行良好,如果 cmdlet 是只有你会使用的东西,我认为风险是可以接受的。
同样,如果在控制台中使用它也能很好地工作,我不知道如果您稍后将详细流重定向到控制台以外的其他地方或没有 "UI"
如果您有更多时间来实施,@PetSerAl 解决方案肯定更合适