65.9K
CodeProject 正在变化。 阅读更多。
Home

双层应用程序,PowerShell 居中

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (5投票s)

2013年11月8日

CPOL

5分钟阅读

viewsIcon

18028

如何在 .NET 应用程序中支持 PowerShell

引言

使用 PowerShell 有多种原因。最显著的优势是可以使用脚本而不是用户界面。对于管理员来说,PowerShell 几乎等同于开发者使用的控制台应用程序。当你的应用程序核心高度独立时,基于它构建一个控制台应用程序会非常容易,这时可以考虑使用 PowerShell 层。

为什么我的应用程序应该使用 PowerShell 接口而不是直接引用库?当开发者想探索一个库时,第一步通常是创建一个控制台应用程序。它是开发者的沙箱,可以快速获得图形化输出,因为每个对象都有一个 ToString 方法。并非所有人都知道如何编写控制台应用程序。管理员喜欢 PowerShell,因为它具有更快的“编写-编译-运行”循环。开发者喜欢能够为每个命令设置断点的代码。所以再次问:为什么是 PowerShell 接口?答案并不令人惊讶——因为它对于特定类型的应用程序非常有用。

背景

为什么我的应用程序应该使用 PowerShell 接口而不是直接引用库?当开发者想探索一个库时,第一步通常是创建一个控制台应用程序。它是开发者的沙箱,可以快速获得图形化输出,因为每个对象都有一个 ToString 方法。并非所有人都知道如何编写控制台应用程序。管理员喜欢 PowerShell,因为它具有更快的“编写-编译-运行”循环。开发者喜欢能够为每个命令设置断点的代码。所以再次问:为什么是 PowerShell 接口?答案并不令人惊讶——因为它对于特定类型的应用程序非常有用。

当代码需要配置某些内容时,实现 PowerShell 支持显然是一项巨大的投资。在我的案例中,我为密码分析库使用了一个 PowerShell 接口。有很多算法,但我不想硬编码解密加密消息的步骤。我想让用户能够使用算法,并基于启发式方法和自己的判断来决定如何组合它们。这与 XAML 中定义的流程类似。PowerShell 最擅长的是快速用户交互,当然,IntelliSense 也不会丢失,因为 PowerShell 具备自动完成功能。

Application layers Application layers

架构很简单。库实现了 PowerShell 接口。用户界面运行一个 PowerShell 主机,向其生成命令,并从其接收对象来填充 ViewModels。由于多种原因,实现此模式并非易事。首先,.NET 主要有两个版本——2.0 和 4.0(1.0 已过时,3.0 和 3.5 是 2.0 的扩展)。PowerShell 有 2 个版本,第三个版本目前处于 beta 阶段。另一个复杂性是双重环境——32 位和 64 位。我花费了许多小时来解决这些方面引起的问题。当最终完成时,我感到有责任写一篇关于它的文章。

首先从库的角度来谈。库必须引用 System.Management.Automation 库,这是 PowerShell SDK 的一部分。有两个重要的类——PSCmdletCustomPSSnapIn。PSCmdlet 是 PowerShell 可以执行并可选择返回一个对象或集合的命令。CustomPSSnapIn 是一个包含供应商信息的 PowerShell 命令集。你的类必须继承它们。Cmdlet 类必须用 Cmdlet 属性进行装饰,而 SnapIn 类必须用 RunInstaller 属性进行装饰。这是反射所必需的,因为 PowerShell 就是通过这种方式在你的库中找到 cmdlet 的。库被编译为 MSIL,因此当你保持 Any CPU 配置时,它在 32 位和 64 位环境中都能工作。你应该选择 .NET Framework 3.5 来对应 PowerShell 2,选择 .NET Framework 4 来对应 PowerShell 3。编译为 .NET 3.5 的 SnapIn 程序集在 PowerShell 3 中应该能正常工作,但反之则不行。

使用代码

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

/* LIBRARY IMPLEMENTING POWERSHELL CMDLETS SAMPLE */

[Cmdlet(VerbsCommunications.Send, "MyCmdlet")]
public class GetMyCmdletCommand : PSCmdlet {

    [Parameter(Mandatory = true, HelpMessage = "This is a sample parameter.")]
    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<CmdletConfigurationEntry>();
        cmdlets.Add(new CmdletConfigurationEntry("Get-MyCmdlet", typeof(GetMyCmdletCommand), null));
    }

    public override string Description {
        get { return "Demo"; }
    }

    public override string Name {
        get { return "SnapInName"; }
    }

    public override string Vendor {
        get { return "Václav Dajbych"; }
    }

    private Collection<CmdletConfigurationEntry> cmdlets;
    public override Collection<CmdletConfigurationEntry> Cmdlets {
        get {
            return cmdlets;
        }
    }
}

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

当用户界面启动 PowerShell 主机时,必须首先使用 RunspaceConfiguration 中的 AddPSSnapIn 类加载 SnapIn。然后应用程序可以调用 PowerShell.Create 方法,并使用库中定义的 PowerShell 命令。PowerShell 3 的优势在于它 构建在 Dynamic Language Runtime 之上。这意味着你可以将 PSObject 类型化为 dynamic,从而缩短你的代码。

/* USER INTERFACE INTERACTION SAMPLE */
  
// PowerShell host
Runspace runSpace;
  
private Init() {
  
  // load PowerShell
  var rsConfig = RunspaceConfiguration.Create();
  runSpace = RunspaceFactory.CreateRunspace(rsConfig);
  runSpace.Open();

  // register 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);
  }

  // load 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;
    }
}

结论

希望这有助于构建友好的配置环境。一旦你知道如何做,你会发现它真的很容易。只需遵循 命名约定

© . All rights reserved.