从C#主机运行Get-Service

时间:2019-05-08 08:51:50

标签: c# powershell

这是一个简单的C#PowerShell主机程序,它仅运行Get-ChildItem并在控制台上显示结果:

using System.Management.Automation;

using static System.Console;

namespace PsHostGetChildItem
{
    class Program
    {
        static void Main(string[] args)
        {
            var result = PowerShell.Create().AddCommand("Get-ChildItem")
                .AddParameter("Path", @"c:\Windows")
                .AddParameter("Filter", "*.exe")
                .Invoke();

            foreach (var elt in result)
                WriteLine(elt);
        }
    }
}

输出内容如下:

enter image description here

如果我们设置了运行Get-Service的类似程序:

using System.Management.Automation;

using static System.Console;

namespace PsHostGetService
{
    class Program
    {
        static void Main(string[] args)
        {
            var result = PowerShell.Create().AddCommand("Get-Service").Invoke();

            foreach (var elt in result)
                WriteLine(elt);
        }
    }
}

它引发以下异常:

enter image description here

让第二个程序输出服务名称的好方法是什么?

2 个答案:

答案 0 :(得分:1)

您需要做更多的工作:

foreach (string str in PowerShell.Create().AddScript("Get-Service").AddCommand("Out-String").Invoke<string>())
{
    Console.WriteLine(str);
}

答案 1 :(得分:1)

您的代码隐式调用.ToString()发出的System.ServiceProcess.ServiceController实例的Get-Service方法,并且该.ToString()方法的成员类型为ScriptMethod,这意味着会在调用时执行脚本块,即一段 PowerShell 代码。

您可以通过交互式执行Get-Service | Get-Member ToString

来查看

问题在于,调用ScriptMethod方法(以及ScriptProperty属性)需要PowerShell runspace 来执行 PowerShell代码 >,并且以后您访问从.Invoke()调用 C#代码返回的对象的成员(方法,属性)时,没有运行空间可用。

解决方法

  • (a)作为您执行的 PowerShell代码的一部分调用成员,以便返回其 value

  • (b)避免使用C#代码中基于脚本的成员,并以不同的方式获得所需的信息。


(a)在PowerShell代码中调用基于脚本的成员:

using (var ps = PowerShell.Create(RunspaceMode.NewRunspace))
{
  foreach (var svcName in ps.AddScript("(Get-Service).ForEach('ToString')").Invoke()) 
  {
    Console.WriteLine(svcName);
  }
}

注意:

  • 此解决方案的缺点是,您只能获取服务的名称,而不能获取描述它们的丰富对象。

  • 使用.AddScript()方法代替.AddCommand(),因为它允许传递完整的PowerShell代码片段(脚本块)以执行。

    • 要在每个元素上执行的方法名称(.ForEach())传递给数组方法'ToString',它需要PSv4 +。在PSv3中,使用ForEach-Object cmdlet。

(b)避免使用C#代码中基于脚本的成员:

在您的情况下,.ToString()只是返回.ServiceName属性的值,因此您可以直接获取该属性的值:

using (var ps = PowerShell.Create())
{
  foreach (var svc in ps.AddCommand("Get-Service").Invoke()) 
  {
    Console.WriteLine(((dynamic) svc).ServiceName);

    // Alternatively, use an explicit cast from .BaseObject:
    // Console.WriteLine(
    //   ((System.ServiceProcess.ServiceController) svc.BaseObject).ServiceName
    // );
  }
}

请注意,需要转换为dynamic才能访问由System.ServiceProcess.ServiceController返回的PSObject实例包装的.Invoke()实例的属性。

或者,也可以通过PSObject的{​​{1}}属性显式地访问包装的对象,该属性允许转换为实型。