当从 C# 以编程方式调用 CmdLet 时,如何捕获 Powershell CmdLet 的详细输出

发布于 2024-07-27 03:27:04 字数 4010 浏览 8 评论 0原文

背景

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

参考

源代码

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

    使用系统; 
      使用系统.管理.自动化;    
    
      命名空间 DemoCmdLet1 
      { 
          班级计划 
          { 
              静态无效主(字符串[]参数) 
              { 
                  var cmd = new GetColorsCommand(); 
    
                      foreach ( cmd.Invoke() 中的 var i) 
                      { 
                         Console.WriteLine("- " + i );    
                      }  
                 }  
             }  
    
          [Cmdlet(“获取”,“颜色”)] 
          公共类 GetColorsCommand :Cmdlet 
          { 
              受保护的重写 void ProcessRecord() 
              { 
                  this.WriteObject("你好"); 
                  this.WriteVerbose("世界"); 
              } 
    
          } 
      } 
      

评论

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

2009-07-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

UPDATE ON 2010-01-16

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

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");
    }
}

BACKGROUND

  • I am using Powershell 2.0 on Windows 7.
  • I am writing a cmdlet in a Powershell module ("module" is new to Powershell 2.0).
  • To test the cmdlet I am writing Unit tests in Visual Studio 2008 that programmatically invoke the cmdlet.

REFERENCE

  • This Article on MSDN called "How to Invoke a Cmdlet from Within a Cmdlet" shows how to call a cmdlet from C#.

THE SOURCE CODE

  • This is a distilled version of my actual code — I've made it as small as possible so that you can see the problem I am having clearly:

    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");
            }
    
        }
    }
    

COMMENTS

  • I understand how to enable and capture verbose output from the Powershell command line; that's not the problem.
  • In this case I am programmatically invoking the cmdlet from C#.
  • Nothing I've found addresses my specific scenario. Some articles suggest I should implement my own PSHost, but seems expensive and also it seems like a have to call the cmdlet as text, which I would like to avoid because that is not as strongly typed.

UPDATE ON 2009-07-20

Here is is the source code based on the answer below.

Some things are still not clear to me:
* How to call the "Get-Colors" cmdlet (ideally without having to pass it as a string to the ps objet)
* How to get the verbose output as it is generated instead of getting an collection of them at the end.

    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");

            }

        }
    }

The code above generates this output:

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

UPDATE ON 2010-01-16

by using the Powershell class (found in System.Management.Automation but only in the version of the assembly that comes with the powershell 2.0 SDK, not what comes out-of-the-box on Windows 7) I can programmatically call the cmdlet and get the verbose output. The remaining part is to actually add a custom cmdlet to that powershell instance - because that was my original goal - to unit test my cmdlets not those that come with 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");
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

眼波传意 2024-08-03 03:27:04
  • 除非将 $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#。

  • Verbose output is not actually output unless $VerbosePreference is set at least to "Continue."
  • Use the PowerShell type to run your cmdlet, and read VerboseRecord instances from the Streams.Verbose propery

Example in powershell script:

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}

This should be easy to translate into C#.

活泼老夫 2024-08-03 03:27:04
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。

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.       }

Important lines are line 5 and 6. This basically set the $verbosepreference for the session and for upcoming new commands and scripts.

帅冕 2024-08-03 03:27:04

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

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

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

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

First off, if you are unit testing cmdlets, likely Pester is a better (and easier) option.

As per your many updates, all you are likely missing at this point is a strongly typed approach to reference the C# cmdlet

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

DISCLAIMER: I know this works for PowerShell 5.0, but don't have experience with the older PowerShell 2.0.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文