【问题标题】:How to capture a Powershell CmdLet's verbose output when the CmdLet is programmatically Invoked from C#从 C# 以编程方式调用 CmdLet 时如何捕获 Powershell CmdLet 的详细输出
【发布时间】:2009-07-18 02:54:18
【问题描述】:

背景

  • 我在 Windows 7 上使用 Powershell 2.0。
  • 我正在 Powershell 模块中编写 cmdlet(“模块”是 Powershell 2.0 的新功能)。
  • 为了测试 cmdlet,我正在 Visual Studio 2008 中编写单元测试,以编程方式调用 cmdlet。

参考

  • This Article on MSDN 名为“如何从 Cmdlet 中调用 Cmdlet”,展示了如何从 C# 调用 cmdlet。

源代码

  • 这是我的实际代码的提炼版本 - 我已将其尽可能小,以便您可以清楚地看到我遇到的问题:

    using System;
    using System.Management.Automation;   
    
    namespace DemoCmdLet1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var cmd = new GetColorsCommand();
    
                    foreach ( var i in cmd.Invoke<string>())
                    {
                       Console.WriteLine("- " + i );   
                    } 
               } 
           } 
    
        [Cmdlet("Get", "Colors")]
        public class GetColorsCommand : Cmdlet
        {
            protected override void ProcessRecord()
            {
                this.WriteObject("Hello");
                this.WriteVerbose("World");
            }
    
        }
    }
    

评论

  • 我了解如何启用和捕获 Powershell 命令行的详细输出;这不是问题。
  • 在这种情况下,我以编程方式从 C# 调用 cmdlet。
  • 我发现没有任何东西可以解决我的具体情况。一些文章建议我应该实现自己的 PSHost,但似乎很昂贵,而且似乎必须将 cmdlet 作为文本调用,我想避免这种情况,因为它不是强类型的。

2009 年 7 月 20 日更新

这是基于以下答案的源代码。

有些事情我还不清楚: * 如何调用“Get-Colors”cmdlet(理想情况下不必将其作为字符串传递给 ps 对象) * 如何在生成时获取详细输出,而不是在最后获取它们的集合。

    using System;
    using System.Management.Automation;   

    namespace DemoCmdLet1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var ps = System.Management.Automation.PowerShell.Create();

                ps.Commands.AddScript("$verbosepreference='continue'; write-verbose 42");

                foreach ( var i in ps.Invoke<string>())
                {
                   Console.WriteLine("normal output: {0}" , i );   
                }
                foreach (var i in ps.Streams.Verbose)
                {
                    Console.WriteLine("verbose output: {0}" , i);
                }

            }
        }

        [Cmdlet("Get", "Colors")]
        public class GetColorsCommand : Cmdlet
        {
            protected override void ProcessRecord()
            {
                this.WriteObject("Red");
                this.WriteVerbose("r");
                this.WriteObject("Green");
                this.WriteVerbose("g");
                this.WriteObject("Blue");
                this.WriteVerbose("b");

            }

        }
    }

上面的代码生成这个输出:

d:\DemoCmdLet1\DemoCmdLet1>bin\Debug\DemoCmdLet1.exe
verbose output: 42

2010 年 1 月 16 日更新

通过使用 Powershell 类(在 System.Management.Automation 中找到,但仅在 powershell 2.0 SDK 附带的程序集版本中,而不是在 Windows 7 上开箱即用的版本中)我可以以编程方式调用cmdlet 并获取详细输出。剩下的部分是实际添加一个自定义 cmdlet 到该 powershell 实例 - 因为这是我最初的目标 - 对我的 cmdlet 而不是 powershell 附带的那些进行单元测试。

class Program
{
    static void Main(string[] args)
    {
        var ps = System.Management.Automation.PowerShell.Create();
        ps.AddCommand("Get-Process");
        ps.AddParameter("Verbose");
        ps.Streams.Verbose.DataAdded += Verbose_DataAdded;
        foreach (PSObject result in ps.Invoke())
        {
            Console.WriteLine(
                    "output: {0,-24}{1}",
                    result.Members["ProcessName"].Value,
                    result.Members["Id"].Value);
        } 
        Console.ReadKey();
    }

    static void Verbose_DataAdded(object sender, DataAddedEventArgs e)
    {
        Console.WriteLine( "verbose output: {0}", e.Index);
    }
}


[Cmdlet("Get", "Colors")]
public class GetColorsCommand : Cmdlet
{
    protected override void ProcessRecord()
    {
        this.WriteObject("Hello");
        this.WriteVerbose("World");
    }
}

【问题讨论】:

  • 我的回答中缺少一个反引号 ` - 这可以防止 $verbosepreference 变量被提前评估。
  • 您为什么没有将我的回复标记为答案?
  • 如果您想要实时详细记录,请订阅 powershell 实例的流属性上的 datachanged 事件。
  • 我已经更新了问题以说明您提供的新信息。我仍然不能说这个问题得到了回答,因为我还没有弄清楚如何将“Get-Colors”cmdlet 添加到 Powershell 实例。
  • 您可能应该问一个新问题。当我回答您的问题时,您在旧问题中添加了一个新问题。

标签: c# powershell powershell-2.0 powershell-cmdlet


【解决方案1】:
  • 除非$VerbosePreference 至少设置为“继续”,否则不会实际输出详细输出。
  • 使用 PowerShell 类型运行 cmdlet,并从 Streams.Verbose 属性中读取 VerboseRecord 实例

powershell 脚本中的示例:

ps> $ps = [powershell]::create()
ps> $ps.Commands.AddScript("`$verbosepreference='continue'; write-verbose 42")
ps> $ps.invoke()
ps> $ps.streams.verbose
Message   InvocationInfo                          PipelineIterationInfo
-------   --------------                          ---------------------
42        System.Management.Automation.Invocat... {0, 0}

这应该很容易翻译成 C#。

【讨论】:

    【解决方案2】:
    1.        string scriptFile = "Test.ps1";
    2.        using (PowerShell ps = PowerShell.Create())
    3.        {
    4.          const string getverbose = "$verbosepreference='continue'"; 
    5.          ps.AddScript(string.Format(getverbose));
    6.          ps.Invoke();
    7.          ps.Commands.Clear();
    8.          ps.AddScript(@".\" + scriptFile);
    9.          ps.Invoke();
    10.         foreach (var v in ps.Streams.Verbose)
    11.         {
    12.               Console.WriteLine(v.Message);
    13.         }
    14.       }
    

    重要的行是第 5 行和第 6 行。这基本上为会话和即将到来的新命令和脚本设置了 $verbosepreference。

    【讨论】:

      【解决方案3】:

      首先,如果您要对 cmdlet 进行单元测试,Pester 可能是一个更好(也更容易)的选择。

      根据您的许多更新,此时您可能缺少的是引用 C# cmdlet 的强类型方法

      ps.AddCommand(new CmdletInfo("Get-MyCS", typeof(GetMyCS)));
      

      免责声明:我知道这适用于 PowerShell 5.0,但没有使用旧 PowerShell 2.0 的经验。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-28
        • 1970-01-01
        • 1970-01-01
        • 2017-12-24
        • 2013-06-08
        • 2017-03-25
        • 2018-12-12
        相关资源
        最近更新 更多