PaaP: 作为平台的Windows PowerShell(一) 13


除了作为一种脚本语言外,Windows PowerShell被多种应用程序使用。这是因为Windows PowerShell引擎可以被托管在一个应用程序内部。这篇博文和下一篇博文将会处理在C#应用程序中托管Windows Powershell的多个API.

用来托管Windows Powershell最为重要的一个类型是System.Management.Automation.PowerShell类。这个类提供了用来创建命令管道和在运行空间中执行命令的方法。

添加命令(AddCommand)

让我们来看一个非常简单的例子。假设你想得到当前机器上运行的进程的一个列表。执行命令的方式如下。

步骤 1 – 创建一个 PowerShell 对象

PowerShell ps = PowerShell.Create();

步骤 2 – 添加你想执行的命令

ps.AddCommand(“Get-Process”);

步骤 3 –执行这些命令

ps.Invoke();

你也可以像这样一步到位执行这些步骤:

PowerShell.Create().AddCommand(“Get-Process”).Invoke();

添加参数(AddParameter)

上面执行单个命令的例子没有任何参数。比方说,我想得到运行在当前机器上的所有PowerShell进程。我就可以使用AddParamete方法来给命令添加参数了。

PowerShell.Create().AddCommand(“Get-Process”)
                   .AddParameter(“Name”, “PowerShell”)
                   .Invoke();

如果想添加更多参数,可以像这样继续调用:

PowerShell.Create().AddCommand(“Get-Process”)
                   .AddParameter(“Name”, “PowerShell”)
                   .AddParameter(“Id”, “12768”)
                   .Invoke();

或者,如果你有一个包含参数名和参数的词典,可以使用AddParameters ,来添加所有参数。

IDictionary parameters = new Dictionary<String, String>();
parameters.Add("Name", "PowerShell");
parameters.Add("Id", "12768");
PowerShell.Create().AddCommand(“Get-Process”)
		   .AddParameters(parameters)
   		   .Invoke()

添加语句(AddStatement)

现在我们已经执行了一个单独的命令和它的参数,让我们再来执行一堆命令。PowerShell API给我们提供了一个模拟批处理的方式,这样就可以在管道的末尾追加额外的语句。获取所有正在运行的进程,然后再获取所有正在运行的服务,可以这样做:

PowerShell ps = PowerShell.Create();
ps.AddCommand(“Get-Process”).AddParameter(“Name”, “PowerShell”);
ps.AddStatement().AddCommand(“Get-Service”);
ps.Invoke();

添加脚本(AddScript)

 AddCommand方法只能添加命令。如果我想运行一个脚本,我可以使用 AddScript 方法。假设我们有一个简单的脚本,a.ps1,可以获取机器上所有运行的PowerShell进程的的数量。

------------D:\PshTemp\a.ps1----------
$a = Get-Process -Name PowerShell
$a.count
------------D:\PshTemp\a.ps1----------
PowerShell ps = PowerShell.Create();
ps.AddScript(“D:\PshTemp\a.ps1”).Invoke();

AddScript API也提供了一个选项来在本地作用域运行脚本。在下面的例子中,脚本会在本地作用域中来执行,因为我们将true传递给了参数useLocalScope 。

PowerShell ps = PowerShell.Create();
ps.AddScript(@“D:\PshTemp\a.ps1”, true).Invoke();

处理错误,详细消息,警告和进度信息(Handling Errors, Verbose, Warning, Debug and Progress Messages)

PowerShell API也能让我们访问命令运行时产生的错误和详细信息等。你可以使用PowerShell.Streams.Error属性获取错误信息,和PowerShell.Streams.Verbose 属性获取详细信息。下面的属性也可以获取进度,调试,和警告信息。

PowerShell ps = PowerShell.Create()
                          .AddCommand("Get-Process")
                          .AddParameter("Name", "Non-ExistentProcess");
ps.Invoke();

ps.Streams.Error有一个命令运行时产生的错误列表,下面的情况,我们在查询一个不存在的进程时得到了一个错误

Get-Process : Cannot find a process with the name "Non-ExistentProcess". 
Verify the process name and call the cmdlet again. 
+ CategoryInfo : ObjectNotFound: (Non-ExistentProcess:String) [Get-Process], 
ProcessCommandException 
+ FullyQualifiedErrorId : 
NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

PowerShell API 非常强大。它能让我们在同步/异步模式下运行命令,创建一个只能运行有限命令集的受约束的运行空间,在嵌套的管道中执行命令,等等。

在本文中的所有例子中,我们创建了一个包含了所有Windows PowerShell 内置命令的默认运行空间,这在内存消耗方面并不高效。在更多的场景中,用户可能想创建一个只包含指定命令集/语言元素的运行空间。在下一篇博文中,我们会解释如何创建一个受限制但是效率更高的运行空间。

原文作者: Indhu Sivaramakrishnan [MSFT] (Windows PowerShell Developer)

原文地址PaaP: Windows PowerShell as a Platform – Part 1

本文链接: https://www.pstips.net/paap-windows-powershell-as-a-platform-part-1.html
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!

关于 Mooser Lee

我是一个Powershell的爱好者,创建了PowerShell中文博客,热衷于Powershell技术的搜集和分享。本站部分内容来源于互联网,不足之处敬请谅解,并欢迎您批评指正。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

13 条评论 “PaaP: 作为平台的Windows PowerShell(一)

  • codecook

    个人理解,Visual Studio不太方便调用powershell里面的远程功能,对对象的二次操作也非常麻烦,以至后来我放弃用它来调用POWERSHELL了,后来发现了powershell studio,这个工具就完美的解决了上述问题。

  • jieche

    两个问题:
    1.PowerShell.Create()每次生成新的ps运行上下文其内存占用是多少,在并发情况下会不会造成内存溢出?
    2.如果使用多个线程共享一个create()实例,如何及时释放?

    • Mooser Lee 文章作者

      1.PowerShell.Create()生成的运行上下文占用的内存不大,我自己写了一个程序测试了下:

              static void Main(string[] args)
              {
                  Console.WriteLine("Before create: {0}kb",MeasureMemory());
                  PowerShell ps = PowerShell.Create();
                  ps.AddCommand("get-process");
                  Console.WriteLine("Before invoke: {0}kb", MeasureMemory());
                  ps.Invoke();
                  Console.WriteLine("After invoke: {0}kb", MeasureMemory());
                  Console.ReadKey();
              }
              static long MeasureMemory()
              {
                  var currentProc=System.Diagnostics.Process.GetCurrentProcess();
                  return currentProc.WorkingSet64/(1024);
              }
      

      机器内存:4G
      输出为:

      Before create: 9736kb
      Before invoke: 11008kb
      After invoke: 38472kb
      

      真正决定内存占用的应当取决于你要运行的命令或者脚本本身的逻辑。
      另外如果想最大限度降低PowerShell运行空间占用的内存可以参考下一篇文章:http://www.pstips.net/paap-windows-powershell-as-a-platform-part-2.html

      2.我个人觉得没必要在线程间共享PowerShell对象。因为PowerShell 中的Job可以完成类似的工作。如果一定要共享,可以使用begininvoke()代替invoke()。这样可以异步执行,并且拿到一个IAsyncResult句柄,判断当前执行是否结束。为了共享,你还得专门写个调度函数,稍显麻烦。

      • jieche

        非常感谢您的回复,昨天通过尝试发现若自己实现create对象共享,在单线程环境中执行多次脚本内存使用会大幅小于每次重新create()对象,但是多线程并发情况下报错: “无法执行此操作,因为命令已启动。请等待该命令完成或停止该命令,然后重试此操作。”,从错误看共享实例不能并发操作,应利用自有ps job调度。因此做web ui时,除非量极小,ps script不宜实时调用

        • Mooser Lee 文章作者

          呵呵,同意您的观点。job才是PowerShell设计者期望用户使用的。
          但是还是有一个途径解决你遇到的并发错误:

          1. 你可以create一个运行空间池,比如池中最大允许10个或者N个instance。
          2. 每次取出一个没有执行过脚本的instance,或者已经执行完毕的instance来执行新的请求。
          3. 一旦运行空间池无空闲instance来执行请求,则等待或者报错。

          这是一个思路,但是不推荐,还是乖乖用job吧!

  • jieche

    你好,我目前有两个问题,
    1.我翻遍了所有博文,我希望有一篇可以介绍C#调用powershell直接使用管道。
    2.C#管理ps script时,如果获取上千台服务器信息用invoke-command foreach每台机器,这样的方式能接受么?

  • jieche

    执行AddScript失败。
    var ps = PowerShell.Create();
    ps.AddCommand(“Get-Process”);
    ps.AddParameter(“Name”, “*a*”);
    ICollection result = ps.Invoke();
    Assert.AreNotEqual(result.Count, 0);//执行获取Process,正确

    ps.AddScript(@”D:\..\scripts\Get-Bios.ps1″);
    ps.AddParameter(“ComputerName”, “localhost”);
    var rt = ps.Invoke();
    Console.WriteLine(rt);//失败,error提示:无法加载文件,因为在此系统上禁止运行脚本。

    ps.AddCommand(“set-executionpolicy”);
    ps.AddParameter(“ExecutionPolicy”, “RemoteSigned”);
    var r2=ps.Invoke();//尝试修改executionpolicy为RemoteSigned,无返回结果

    var r3 = ps.AddCommand(“get-executionpolicy”).Invoke();//返回结果还是:Restricted,继续执行AddScript依然提示“无法加载文件”

    代码哪里出了问题?

    • Mooser Lee 文章作者

      两点:
      1.set-executionpolicy是一条交互式的命令,默认需要用户的确认,比如“yes”或者“y‘,才能生效,你需要加上-force参数,跳过这个确认步骤
      2. set-executionpolicy需要管理员权限,你能确认你托管powershell 的 c# 程序是以管理员权限运行的吗?如果不能,参考http://stackoverflow.com/questions/2818179/how-to-force-my-net-app-to-run-as-administrator-on-windows-7

      建议:一般我在一些自动化的项目中发现,大家喜欢这样做,来解决策略问题:
      先用c# 调用 powershell.exe ,传入参数 -Command “& {Set-ExecutionPolicy Unrestricted -Force}”,可以参考:System.Diagnostics.Process类

      • jieche

        关于权限问题,正是我想问的。
        1.set-executionpolicy需要管理员权限,PowerShell.Create()创建的实例默认脚本权限为restrict,如此的话作为宿主应用程序或服务必须用admin作为运行账号吗?如果是必须这是个严重的问题
        2.此方法: cmd-》powershell.exe -Command “” 如何解决策略的,没看明白。其一样要求admin运行账号的。