使用PowerShell接口增强应用程序

PowerShell是一种强大的命令行工具,它为管理员和开发者提供了一个几乎相同的环境。当应用程序的核心是高度独立的,那么基于它构建一个控制台应用程序是极其容易的,考虑使用 PowerShell 层。

为什么要在应用程序中使用 PowerShell 接口而不是直接引用库?当开发者想要探索一个库时,通常的第一步是创建一个控制台应用程序。它是开发者的沙箱,可以快速地以图形方式输出,因为每个对象都有一个 ToString 方法。并非每个人都知道如何编写控制台应用程序。管理员喜欢 PowerShell,因为编写-编译-运行循环要快得多。开发者喜欢代码,因为可以在每个命令上设置断点。那么,为什么选择 PowerShell 接口呢?答案并不令人惊讶——因为它对于特定类型的应用程序非常有用。

当代码配置某些东西时,很明显实现 PowerShell 支持是一项很好的投资。在例子中,为一个密码分析库使用了一个 PowerShell 接口。有许多算法,但不想硬编码解密加密消息的步骤。想让用户能够使用算法,并根据启发式和自己的判断决定哪些算法组合在一起。这类似于在 XAML 中定义的工作流。PowerShell 最擅长的是快速用户交互,当然,由于 PowerShell 具有自动完成功能,IntelliSense 也不会丢失。

架构

架构很简单。库实现了 PowerShell 接口。用户界面运行一个 PowerShell 主机,生成命令并接收对象以填充视图模型。实现这种模式并不简单,原因有很多。首先,基本上有两个版本的 .NET——2.0 和 4.0(1.0 已经过时,3.0 和 3.5 是 2.0 的扩展)。PowerShell 有两个版本,第三个目前处于 beta 阶段。另一个复杂性是双环境——32 位和 64 位。花了很长时间解决这些问题。当最终完成后,觉得有责任写一篇文章。

库必须引用 System.Management.Automation 库,这是PowerShellSDK 的一部分。有两个重要的类——PSCmdlet 和 CustomPSSnapIn。PSCmdlet 是 PowerShell 可以执行的命令,可以选择性地返回一个对象或集合。CustomPSSnapIn 是一组 PowerShell 命令,包含在一个包中的供应商信息。类必须从它们派生。Cmdlet 类必须用 Cmdlet 属性装饰,SnapIn 类必须用 RunInstaller 属性装饰。这是反射所必需的,因为这就是 PowerShell 在库中找到 cmdlets 的方式。库被编译成 MSIL,所以当保持 Any CPU 配置时,它可以在 32 位和 64 位环境中工作。应该选择 .NET Framework 3.5 对应 PowerShell 2,.NET Framework 4 对应 PowerShell 3。编译为 .NET 3.5 的 SnapIn 程序集应该可以在 PowerShell 3 中正常工作,但反之则不行。

包含 cmdlets 的库必须注册。有一个 InstallUtil.exe 工具可以完成这项工作。问题是这个工具是平台依赖的。避免猜测哪个框架版本正在运行,或者是否使用 32 位或 64 位变体,需要知道这个工具只是一个 AssemblyInstaller 类的包装器。这意味着在应用程序启动期间,库可以轻松注册,因此托管 PowerShell 知道它的存在。在应用程序退出时,库被注销,所以它的位置可以在将来更改,而不会引起任何错误。这种注册方法并不适合所有场景。库的注册应该由安装程序而不是应用程序本身来完成,但是谁喜欢安装程序呢?使用 PowerShell 的用户通常关闭用户帐户保护,因为他们非常清楚自己在做什么。

[Cmdlet(VerbsCommunications.Send, "MyCmdlet")] public class GetMyCmdletCommand : PSCmdlet { [Parameter(Mandatory = true, HelpMessage = "这是一个示例参数。")] public string Param { get; set; } protected override void ProcessRecord() { var obj = new ObjectToReturn(Param); WriteObject(obj); } } [RunInstaller(true)] public class Cryptanalysis : CustomPSSnapIn { public Cryptanalysis() { cmdlets = new Collection(); cmdlets.Add(new CmdletConfigurationEntry("Get-MyCmdlet", typeof(GetMyCmdletCommand), null)); } public override string Description { get { return "演示"; } } public override string Name { get { return "SnapInName"; } } public override string Vendor { get { return "Václav Dajbych"; } } private Collection cmdlets; public override Collection Cmdlets { get { return cmdlets; } } }

用户界面的角度更困难。首先值得知道的是,即使是 64 位应用程序也会运行 32 位 PowerShell 实例。应用程序的 .NET Framework 版本并不重要。重要的是能够引用 System.Management.Automation 库。这个库位于 v1.0 目录中,但这并不意味着使用了 PowerShell 1。总是使用最新可用的版本。

// PowerShell 主机 Runspace runSpace; private Init() { // 加载 PowerShell var rsConfig = RunspaceConfiguration.Create(); runSpace = RunspaceFactory.CreateRunspace(rsConfig); runSpace.Open(); // 注册 snapin using (var ps = PowerShell.Create()) { ps.Runspace = runSpace; ps.AddCommand("Get-PSSnapin"); ps.AddParameter("Registered"); ps.AddParameter("Name", "SnapInName"); var result = ps.Invoke(); if (result.Count == 0) Register(false); } // 加载 snapin PSSnapInException ex; runSpace.RunspaceConfiguration.AddPSSnapIn("SnapInName", out ex); } void Register(bool undo) { var core = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "MySnapInLib.dll"); using (var install = new AssemblyInstaller(core, null)) { IDictionary state = new Hashtable(); install.UseNewContext = true; try { if (undo) { install.Uninstall(state); } else { install.Install(state); install.Commit(state); } } catch { install.Rollback(state); } } } dynamic DoJob(string parameter) { using (var ps = PowerShell.Create()) { ps.Runspace = runSpace; ps.AddCommand("Get-MyCmdlet"); ps.AddParameter("Param", parameter); dynamic result = ps.Invoke().Single(); return result.ReturnedObjectProperty; } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485