通过


为 .NET 项目选择正确的 PowerShell NuGet 包

除了随每个 PowerShell 版本一起发布的 pwsh 可执行包外,PowerShell 团队还维护了 NuGet上可用的多个包。 这些包允许将 PowerShell 作为 .NET 中的 API 平台作为目标。

作为提供 API 并期望加载实现其自己的 (二进制模块)的 .NET 库的 .NET 应用程序,PowerShell 必须以 NuGet 包的形式提供。

目前有几个 NuGet 包提供 PowerShell API 外围区域的一些表示形式。 哪个软件包适合用于特定项目并不总是显而易见的。 本文介绍了一些面向 PowerShell 的 .NET 项目的常见方案,以及如何为面向 PowerShell 的 .NET 项目选择合适的 NuGet 包。

托管与引用

一些 .NET 项目试图编写要加载到预先存在的 PowerShell 运行时(例如 pwshpowershell.exePowerShell 集成控制台或 ISE)中的代码,而另一些项目则希望在其自己的应用程序中运行 PowerShell。

  • 引用 用于将项目(通常是模块)加载到 PowerShell 中。 必须针对 PowerShell 提供的 API 编译模块,以便与之交互。 但 PowerShell 通过加载它来提供实现。 项目可以使用 引用程序集 或实际的运行时程序集作为编译目标,但必须确保它不会在构建时发布任何这些程序集。
  • 托管 是在项目需要自己的 PowerShell 实现时,通常是因为它是需要运行 PowerShell 的独立应用程序。 在这种情况下,不能使用纯引用程序集。 而是必须依赖具体的 PowerShell 实现。 由于必须使用具体的 PowerShell 实现,因此必须选择特定版本的 PowerShell 进行托管;单个主机应用程序不能使用多目标 PowerShell 版本。

发布以 PowerShell 作为引用的项目

注意

我们在本文中使用术语发布来指代运行 dotnet publish,该运行会将 .NET 库放入包含其所有依赖项的目录中,以准备好部署到特定的运行时。

设置 PrivateAssets 属性以防止发布刚刚用作编译引用目标的项目依赖项:

<PackageReference Include="PowerShellStandard.Library" Version="5.1.0.0" PrivateAssets="all" />

如果未设置此属性并使用引用程序集作为目标,则可以看到与使用引用程序集的默认实现而不是实际实现相关的问题。 例如,你可以收到一个 NullReferenceException,因为引用程序集通常通过返回 null来模拟实现 API。

面向 PowerShell 的 .NET 项目的关键类型

尽管任何 .NET 库或应用程序都可以嵌入 PowerShell,但有一些使用 PowerShell API 的常见方案:

  • 实现 PowerShell 二进制模块

    PowerShell 二进制模块是由 PowerShell 加载的 .NET 库。它必须实现 PowerShell API(例如 PSCmdletCmdletProvider 类型),才能分别公开 cmdlet 或提供程序。 由于它们已加载到其中,因此模块会尝试针对对 PowerShell 的引用进行编译,而无需将其发布到其生成中。 模块也经常需要支持多个 PowerShell 版本和平台,理想情况下,磁盘空间、复杂性或重复实现的开销最少。 有关详细信息,请参阅 about_Modules

  • 实现 PowerShell 主机

    PowerShell 主机为 PowerShell 运行时提供交互层。 它是一种特定的 托管形式, 其中 PSHost 作为 PowerShell 的新用户界面实现。 例如,PowerShell ConsoleHost 为 PowerShell 可执行文件提供终端用户界面,而 PowerShell 编辑器服务主机和 ISE 主机都围绕 PowerShell 提供编辑器集成的部分图形用户界面。 虽然可以将主机加载到现有的 PowerShell 进程中,但更常见的是,主机实现作为独立的 PowerShell 实现,并且重新分发 PowerShell 引擎。

  • 从其他 .NET 应用程序调用 PowerShell

    与任何应用程序一样,可以调用 PowerShell 作为子进程来运行工作负荷。 但是,作为 .NET 应用程序,还可以调用进程内 PowerShell 来恢复完整的 .NET 对象,以便在调用应用程序中使用。 这是一种更常规的 托管形式,应用程序在其中保存自己的 PowerShell 实现供内部使用。 例如,可以创建运行 PowerShell 的服务或守护程序来管理计算机状态,或者请求运行 PowerShell 的 Web 应用程序,以执行某些工作,例如管理云部署。

  • 单元测试 .NET 中的 PowerShell 模块

    通常,应该从 PowerShell 测试旨在向 PowerShell 公开功能的模块和其他库。 有时需要使用 .NET 对为 PowerShell 模块编写的 API 进行单元测试。

PowerShell NuGet 包概览

以下 NuGet 包公开 PowerShell API:

  • PowerShellStandard.Library 是一个引用程序集,用于生成可在多个 PowerShell 运行时中加载的单个程序集。
  • Microsoft.PowerShell.SDK,用于定位和重新托管整个 PowerShell SDK 的方式
  • System.Management.Automation 包是核心 PowerShell 运行时和引擎实现,适用于最少的托管实现以及特定于版本的目标设定场景。
  • Windows PowerShell 引用程序集用于面向 Windows PowerShell(PowerShell 版本 5.1 及更低版本)并对其进行有效托管。

注意

PowerShell NuGet 包根本不是 .NET 库包,而是提供 PowerShell dotnet 全局工具实现。 不要在任何项目中使用此包,因为它仅提供可执行文件。

PowerShellStandard.Library

PowerShell Standard 库是一个参考程序集,用于捕获 PowerShell v7 和 v5.1 API 的交集部分。 它提供编译时检查的 API 图面来编译 .NET 代码,从而允许 .NET 项目以 PowerShell v7 和 v5.1 为目标,而不会冒调用不可用的 API 的风险。

使用 PowerShell Standard 编写 PowerShell 模块或其他仅打算在将它加载到 PowerShell 进程中后运行的代码。 由于它是引用程序集,因此 PowerShell Standard 不包含任何实现本身,因此它不提供独立应用程序的功能。

将 PowerShell Standard 用于不同的 .NET 运行时

PowerShell Standard 面向 .NET Standard 2.0 目标运行时,后者是外观运行时,旨在提供 .NET Framework 和 .NET Core 共享的公共外围应用。 它允许你以单个运行时为目标来生成一个程序集,该程序集适用于多个 PowerShell 版本,但会产生以下后果:

  • 加载模块或库的 PowerShell 实例必须至少运行 .NET 4.6.1;.NET 4.6 和 .NET 4.5.2 不支持 .NET Standard。

    注意

    较新的 Windows PowerShell 版本并不意味着较新的 .NET Framework 版本。 Windows PowerShell 5.1 可以在 .NET 4.5.2 上运行。

  • 若要使用运行 .NET Framework 4.7.1 或更高版本的 PowerShell,需要 .NET 4.6.1 NETStandard.Library 实现才能在旧版 .NET Framework 中提供 netstandard.dll 和其他填充码程序集。

PowerShell 6(及更高版本)提供自己的填充码程序集,用于从 .NET Framework 4.6.1 (及更高版本)到 .NET Core 的类型转发。 只要模块只使用 .NET Core 中存在的 API,PowerShell 6(及更高版本)就可以在为 .NET Framework 4.6.1( net461 运行时目标)生成时加载并运行它。

使用 PowerShell Standard 面向具有单个已发布 DLL 的多个 PowerShell 版本的二进制模块有两个选项:

  1. net461 目标运行时构建的程序集的发布包含:

    • net461 运行时发布项目
    • 此外,针对 netstandard2.0 运行时(不使用其生成输出)进行编译,以确保使用的所有 API 也存在于 .NET Core 中。
  2. 发布用于 netstandard2.0 目标运行时的程序集构建需要:

    • netstandard2.0 运行时发布项目
    • 获取 NETStandard.Library 的 net461 依赖项并将其复制到项目程序集的发布位置,以便在 .NET Framework 中对程序集进行正确类型转发。

若要生成面向旧版 .NET Framework 的 PowerShell 模块或库,你可能更倾向于为多个 .NET 运行时进行构建。 这样做会为每个目标运行时发布一个程序集。 正确的程序集必须在模块加载时加载,例如,将小 .psm1 文件作为根模块。

在 .NET 中测试 PowerShell 标准项目

在 .NET 中使用测试运行程序(如 xUnit)测试模块时,请记住编译时检查是有限的。 您必须针对相关的 PowerShell 平台测试您的模块。

若要在 .NET 中测试针对 PowerShell Standard 生成的 API,应将 Microsoft.PowerShell.SDK 添加为具有 .NET Core 的测试依赖项(版本设置为与所需的 PowerShell 版本匹配),以及具有 .NET Framework 的相应 Windows PowerShell 引用程序集。

有关 PowerShell Standard 及其编写适用于多个 PowerShell 版本的二进制模块的详细信息,请参阅 此博客文章。 另请参阅 GitHub 上的 PowerShell 标准存储库

Microsoft.PowerShell.SDK

Microsoft.PowerShell.SDK 是元包,将 PowerShell SDK 的所有组件拉到单个 NuGet 包中。 独立 .NET 应用程序可以使用 Microsoft.PowerShell.SDK 运行任意 PowerShell 功能,而无需依赖于任何外部 PowerShell 安装或库。

注意

PowerShell SDK 仅指构成 PowerShell 的所有组件包,可用于使用 PowerShell 进行 .NET 开发。

给定 Microsoft.PowerShell.SDK 版本包含相同版本的 PowerShell 应用程序的具体实现。 版本 7.0 包含 PowerShell 7.0 的实现。 命令或脚本的行为类似于在 PowerShell 7.0 中运行它们。 但是,从 SDK 运行 PowerShell 命令与从 pwsh中运行命令不同。 例如, Start-Job 依赖于 pwsh 可用的可执行文件,因此它默认不适用于 Microsoft.PowerShell.SDK 该可执行文件。

通过将 .NET 应用程序中的 Microsoft.PowerShell.SDK 作为目标,你可以与 PowerShell 的所有实现程序集(例如 System.Management.AutomationMicrosoft.PowerShell.Management 和其他模块程序集)集成。

发布以Microsoft.PowerShell.SDK为目标的应用程序包括所有这些程序集和 PowerShell 所需的所有依赖项。 它还包括 PowerShell 在其构建过程中所需的其他资产,例如 Microsoft.PowerShell.* 模块的模块清单,以及 ref 目录,这是 Add-Type 所需的。

Microsoft.PowerShell.SDK 最适合以下内容:

  • PowerShell 主机的实现。
  • 对面向 PowerShell 引用程序集的库进行 xUnit 测试。
  • 在 .NET 应用程序中进程内调用 PowerShell。

可以将 Microsoft.PowerShell.SDK 用作 .NET 项目的参考目标,以创建由 PowerShell 加载的模块或程序集,这些模块或程序集依赖于仅存在于特定版本 PowerShell 中的 API。 为特定版本的程序集 Microsoft.PowerShell.SDK 发布的程序集只能在该版本的 PowerShell 中加载和使用。 若要定位具有特定 API 的多个 PowerShell 版本,需要多个构建,每个构建都针对其自身的 Microsoft.PowerShell.SDK版本。

注意

PowerShell SDK 仅适用于 PowerShell v6 及更高版本。 若要通过 Windows PowerShell 提供等效功能,请使用本文后面介绍的 Windows PowerShell 引用程序集。

System.Management.Automation

System.Management.Automation 包是 PowerShell SDK 的核心。 该包主要位于 NuGet 上,作为供 Microsoft.PowerShell.SDK 引入的资产。 不过,该包也可以直接用作包,用于较小的托管方案和面向版本的模块。

具体而言,在以下情况下,包 System.Management.Automation 是首选包:

  • 你只想使用命名空间中的 System.Management.Automation.Language PowerShell 语言功能,例如 PowerShell 分析器、AST 和 AST 访问者 API。
  • 你只想从 Microsoft.PowerShell.Core 模块执行特定命令,并且可以在使用 CreateDefault2 工厂方法创建的会话状态中执行这些命令。

此外,在以下情况下,System.Management.Automation 是一个有用的参考程序集:

  • 你希望以仅在特定 PowerShell 版本中存在的 API 为目标
  • 你不依赖于发生在程序集 System.Management.Automation 之外的类型,例如在模块 Microsoft.PowerShell.* 中由 cmdlet 导出的类型。

Windows PowerShell 引用程序集

对于 Windows PowerShell 版本 5.1 及更早版本,没有 SDK 提供 PowerShell 的实现,因为 Windows PowerShell 的实现是 Windows 的一部分。 相反,Windows PowerShell 引用程序集不仅提供引用目标,还提供一种重新托管 Windows PowerShell 的方法,其作用与 PowerShell SDK 在版本 6 及更高版本中的作用相同。 对于每个版本的 Windows PowerShell,Windows PowerShell 引用程序集有不同的包:

可以在 Windows PowerShell SDK 中找到有关如何使用 Windows PowerShell 引用程序集的信息。

使用这些 NuGet 包的实际示例

不同的 PowerShell 工具项目针对不同的 PowerShell NuGet 包,具体取决于其需求。 下面列出了一些值得注意的示例。

PSReadLine 系列

PSReadLine 是提供大部分 PowerShell 丰富控制台经验的 PowerShell 模块,将 PowerShell Standard(而不是特定的 PowerShell 版本)作为依赖项目标,并面向 net461 中的 .NET 运行时。

PowerShell v6(及更高版本)提供自己的适配程序集,使针对net461运行时的DLL在加载时能够顺利运行。 垫片简化了 PSReadLine 的分发和模块布局。 PowerShell Standard 确保模块仅使用 Windows PowerShell 5.1 和 PowerShell 6(及更高版本)中可用的 API,该 API 允许模块仅附带单个程序集。

.NET 4.6.1 目标确实意味着不支持在 .NET 4.5.2 和 .NET 4.6 上运行的 Windows PowerShell。

PowerShell 编辑器服务

PowerShell 编辑器服务(PSES)是用于 Visual Studio CodePowerShell 扩展的后端。 此 PowerShell 模块由 PowerShell 加载,然后接管该过程以在自身内重新托管 PowerShell,同时提供语言服务协议和调试适配器功能。

PSES 为 netcoreapp2.1 提供了针对 PowerShell 6+ 的具体实现目标(因为 PowerShell 7 的 netcoreapp3.1 运行时向后兼容),为 net461 提供了针对 Windows PowerShell 5.1 的实现目标,但大部分逻辑包含在面向 netstandard2.0 和 PowerShell Standard 的第二个程序集里。 此设计允许它拉取 .NET Core 和 .NET Framework 平台所需的依赖项,同时仍简化了统一抽象背后的大部分代码库。

由于 PSES 面向 PowerShell Standard,因此需要正确测试运行时实现。 为此,PSES 的 xUnit 测试会调用Microsoft.PowerShell.SDKMicrosoft.PowerShell.5.ReferenceAssemblies,在测试环境中实现 PowerShell 功能。

由于 PSES 不支持 .NET 4.6 及更早版本,因此在运行时 执行检查 ,然后再调用任何可能导致 .NET Framework 运行时崩溃的 API。

PSScriptAnalyzer

PSScriptAnalyzer(用于 PowerShell 的 Linter)必须面向仅在某些版本的 PowerShell 中引入的语法元素。 由于通过实现 AstVisitor2来实现这些语法元素的识别,因此无法使用 PowerShellStandard 并实现较新的 PowerShell 语法的 AST 访问者方法。

相反,PSScriptAnalyzer 将每个 PowerShell 版本 作为生成配置为目标,并为每个版本生成单独的 DLL。 这会增加构建大小和复杂性,但允许:

  • 针对特定版本的 API 定位
  • 实现版本特定功能时,几乎不会增加运行时成本
  • 对 .NET Framework 4.5.2 及更高版本上运行的 Windows PowerShell 的总支持

总结

本文列出了并讨论了可以针对的 NuGet 包以及使用每个包的原因。 一般建议包括:

  • 如果 PowerShell 模块 仅需要不同 PowerShell 版本通用的 API,则它们应针对 PowerShell 标准版进行编译。
  • 需要内部运行 PowerShell 的 PowerShell 主机和应用程序 应面向以下目标:
    • 适用于 PowerShell v6 及更高版本的 PowerShell SDK
    • 或适用于 Windows PowerShell 的相关 Windows PowerShell 参考程序集
  • 需要 特定于版本的 API 的 PowerShell 模块应面向所需 PowerShell 版本的 PowerShell SDK 或 Windows PowerShell 引用程序集。 将它们用作引用程序集,因此不会发布 PowerShell 依赖项。