2016-11-10 85 views
0

我目前正在使用PowerShell 5.0 SDK编写C#cmdlet。Cmdlet详细流

我试图将“第三方可执行文件的StandardError”管道从“实时”从powershell运行时输出到cmdlet输出。

我目前使用MedallionShell库来处理运行过程。我已经用普通的C#win窗体尝试了这一点,并使用Command.StandardError.PipeToAsync(Console.OpenStandardOutput())来获取打印输出,因为可执行文件以“实时”方式将其生成到控制台。

我试图创建我自己的Stream对象,它调用WriteVerbose,但它似乎没有打印任何东西到powershell屏幕(我正在传递-Verbose到cmdlet,当我运行它)。

我的电流看起来是这样的:

  1. 打开PowerShell ISE中
  2. 加载我模块(C#DLL)
  3. 叫我的cmdlet和参数
    • Command.Run
    • Command.StandardError.PipeToAsync(???)
    • Command.Wait(在此步骤中,o输出应该流向PowerShell窗口)
    • 检查Command.Result.Success。

任何人都可以点我在这个正确的方向?

+0

难道你自己'Stream'实现通话'WriteVerbose'直接或他们元帅管线螺纹? – PetSerAl

+0

我的流的实现采用了该cmdlet并用它来调用WriteVerbose。我从Write调用收到的字节,我使用Encoding.UTF8.GetString(字节)转换为字符串。 –

+0

这不是我问的问题。你不能在任意线程上调用'WriteVerbose'。那么,你是否调用了'WriteVerbose'回到管道线程? – PetSerAl

回答

0

您不能从任意线程调用CmdletWrite方法(如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 
+0

你的解释是有道理的,我做了WriteVerbose会知道如何处理电话的假设,无论我在哪里。然而,我并不完全理解你的代码示例,主要是变量'numberOfCompleteRequests','complete'操作和foreach。这个foreach是否永远运行直到'CompleteAdding'被调用? == 3检查的目的是什么? –

+0

@AbdulKhan *这个foreach是否永远运行直到'CompleteAdding'被调用?* - 是的。 *'complete'action * - 我不想重复代码,但我也想访问局部变量'messageQueue'和'numberOfCompleteRequests',所以我用lambda表达式定义'complete'以关闭局部变量。 * == 3检查的目的是什么?* - 我想在继续之前发生3个事件:输出流关闭,错误流关闭和流程退出,所以我检查了'complete()'调用了3次。当然,你可以选择不同的事件。 – PetSerAl

+0

非常感谢,这个解释帮助我编写了适合我的问题的消息循环版本。 –