在此系列文章的前一篇,我们看到了怎样使用System.Management.Automation.PowerShell 类来在c#应用程序中运行PowerShell 命令。在那些例子中,我们创建的都是默认的运行空间。在这篇文章中,我们一起来看怎样自定义运行空间?比如,可能存在一种情况,你不想用户可以使用那些能更改机器状态的特定命令。这样,你现在可以创建一个不包含那些命令的命名空间,这样用户就访问不到它们了。我们看看应当怎么做。有很多类型可以被用来托管Windows PowerShell。下面列出其中最重要的几种托管Windows PowerShell的类型(作为基本托管场景)
- Runspace
- InitialSessionState
- PowerShell (前一篇文章中已经详细介绍过)
我们来看一个非常简单的托管Windows PowerShell的例子。
PowerShell ps = PowerShell.Create(); ps.AddCommand("Get-Command") .AddParameter("CommandType","Cmdlet") .AddParameter("ListImported","true"); ps.Invoke();
该示例创建了一个PowerShell对象。不怎么明显,但是当你使用Create()方法时,就已经创建了一个运行空间。默认情况下,它会把PowerShell 内置的命令全部加载进运行空间。在Windos 8上运行上面的命令(在运行空间中列出的命令),会添加259条命令。内存使用并不高效。
在很多情况下,人们想创建一个只包含特定命令集合和语言元素的运行空间。只包含了有限的命令集合的运行空间被称为“约束运行空间”,详细可以参考这里。要创建一个约束运行空间,我们需要创建一个“初始会话状态”(InitialSessionState)对象,用来限制用户用什么命令。
创建一个托管Windows PowerShell的约束运行空间需要4个步骤:
- 创建一个InitialSessionState(InitialSessionState可以认为是一个在运行空间打开时,用来存储可用命令集的一个容器 。)
- 向InitialSessionState中添加你需要的命令。
- 为初始会话状态创建一个运行空间 ( 一个运行空间是Windows PowerShell 能够运行的环境。)
- 打开运行空间,让运行空间就绪等待命令执行。
- 基于该运行空间来创建一个PowerShell对象,添加命令,然后执行命令。(这部分和上一篇文章中演示的一样)
使用所有内置的核心命令来创建InitialSessionState
下面是一个使用约束运行空间技术根据所有核心命令来创建初始会话状态的例子。使用 CreateDefault()方法创建一个初始会话状态,然后根据初始会话状态创建一个运行空间,打开这个运行空间,再运行运行空间中的命令。步骤四中的命令使用了PowerShell 对象中的Runspace属性,指定自定义运行空间(rs)。
(在这个步骤中我们跳过了步骤二(添加我们期望的命令)。我会在下一个例子中解释那部分。)
步骤 1
InitialSessionState iss = InitialSessionState.CreateDefault();
步骤 2
Runspace rs = RunspaceFactory.CreateRunspace(iss);
步骤 3
rs.Open();
步骤 4
PowerShell ps = PowerShell.Create(); ps.Runspace = rs; ps.AddCommand("Get-Command"); ps.Invoke();
InitialSessionState 有三个不同的方法来创建存储命令的容器
- Create – 创建一个空的容器。容器中没有添加任何命令。
- CreateDefault – 创建一个包含了机器上所有Windows PowerShell 内置命令的会话状态。使用这个API,所有PowerShell内置的的命令都会被以管理单元的形式加载进去。
- CreateDefault2 – 创建一个仅包含托管Windows PowerShell 最小集合的命令集。使用这个API,只会有一个管理单元Microsoft.PowerShell.Core被加载。
上一个例子中创建的InitialSessionState对象,默认加载了所有Windows PowerShell内置的命令。我们可以通过创建一个空的InitialSessionState对象,然后添加我们所需的命令,来进一步约束它。
创建一个空的InitialSessionState,并添加命令
在这个示例中,我们创建一个空的初始会话状态,然后使用SessionStateCmdletEntry 类来为初始会话状态定义命令集。使用这个类,我们分别为Get-Command和Import-Module创建了一个命令项,然后将这些命令项添加进初始会话状态。这就使这些命令能在会话中的管道中运行了。其结果就是那个会话中仅仅包含了Get-Command和Import-Module两个命令。
步骤 1 – 创建一个空的InitialSessionState对象
InitialSessionState iss = InitialSessionState.Create();
步骤 2 – 添加要运行的命令
SessionStateCmdletEntry getCommand = new SessionStateCmdletEntry( "Get-Command", typeof(Microsoft.PowerShell.Commands.GetCommandCommand), ""); SessionStateCmdletEntry importModule = new SessionStateCmdletEntry( "Import-Module", typeof(Microsoft.PowerShell.Commands.ImportModuleCommand), ""); iss.Commands.Add(getCommand); iss.Commands.Add(importModule);
步骤 3 – 创建运行空间
Runspace rs = RunspaceFactory.CreateRunspace(iss);
步骤 4 – 打开运行空间
rs.Open();
步骤 5 – 执行运行空间中的命令
PowerShell ps = PowerShell.Create(); ps.Runspace = rs; ps.AddCommand("Get-Command"); Collection<CommandInfo> result = ps.Invoke<CommandInfo>(); foreach (var entry in result) { Console.WriteLine(entry.Name); }
输出
Get-Command Import-Module
我们在这里创建的运行空间仅包含了 Get-Command和Import-Module两条命令。创建了一个空的 InitialSessionState,然后添加命令。这使得应用程序在内存使用上,非常高效。在上一个例子中,我们也可以通过显式的创建一个运行空间来完成它。System.Management.Automation.PowerShell 类有一个公共的API Create,可以接收InitialSessionState参数。使用此版本,步骤3,4,和5可以被下面的命令所代替。
PowerShell ps = PowerShell.Create(iss); ps.AddCommand("Get-Command"); Collection<CommandInfo> result = ps.Invoke<CommandInfo>(); foreach (var entry in result) { Console.WriteLine(entry.Name); }
PowerShell 运行空间性分析
下面这张图表展示了使用不同方法来创建InitialSessionState的执行时间。
- PowerShell 运行空间性能图
正如在这些例子中看到的那样,InitialSessionState提供了一个非常优秀的机制来创建一个仅包含了你想暴露的命令的运行空间。
Windows PowerShell SDK 已经包含了许多托管Windows PowerShell的例子。更多关于Windows PowerShell SDK 的信息可以参考这里。
原文作者: Indhu Sivaramakrishnan(Windows PowerShell Developer)
原文链接:PaaP: Windows PowerShell as a Platform – Part 2
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!
我希望在程序中使用这个方式,减低内存使用率:
SessionStateCmdletEntry getCommand = new SessionStateCmdletEntry(
“Get-Command”, typeof(Microsoft.PowerShell.Commands.GetCommandCommand), “”);
我可以用此方法加入全部的ps command么? 例如:
Invoke-Command
set-executionpolicy
Get-Service
Get-CimInstance
我尝试了,在Microsoft.PowerShell.Commands中很多是无法找到对应名称的
1.这是一个取舍问题,如果你要使用的命令,超出了最小约束空间,那你只能使用更大的约束空间,或者无约束了。
2. 那些命名已经内置了运行空间,可以参考:http://msdn.microsoft.com/en-us/library/microsoft.powershell.commands(v=vs.85).aspx
3. 也就是上面链接中的一句话:自定义(Similarly, it is strongly suggested that you create your own Commands namespace for those cmdlets and providers that you implement. )
为何我本地的Microsoft.PowerShell.Commands中列出的命令数远远小于http://msdn.microsoft.com/en-us/library/microsoft.powershell.commands(v=vs.85)给出的。例如:ConvertTo-Csv,Get-WmiObject就都没有。
我的运行环境是vs2012,ps3.0,.net framework4.5
对不起,经确认,确实上面页面显示的类,没有在c#中全部列出。看来只能使用CreateDefault ()方法了。
那这不是形同鸡肋吗?
是啊,这事他们没少干过。