我最近对模块有一个想法,我想作为二进制模块实现。 我还没有使用 PowerShell 标准库创建过模块,这看起来是个不错的机会。 我使用了 创建跨平台二进制模块 指南来创建此模块,没有任何障碍。 我们会重复相同的过程,我将在过程中加入一些额外的评论。
注意
本文的 原始版本 出现在 @KevinMarquette撰写的博客上。 PowerShell 团队感谢 Kevin 与我们共享此内容。 请在 PowerShellExplained.com查看他的博客。
什么是 PowerShell 标准库?
借助 PowerShell 标准库,我们可以创建可在 PowerShell 和 Windows PowerShell 5.1 中运行的跨平台模块。
为什么是二进制模块?
在 C# 中编写模块时,你将失去对 PowerShell cmdlet 和函数的轻松访问。 但是,如果要创建不依赖于许多其他 PowerShell 命令的模块,则性能优势可能很大。 PowerShell 已针对管理员而不是计算机进行优化。 通过切换到 C#,可以摆脱 PowerShell 添加的开销。
例如,我们有一个关键流程要通过 JSON 和哈希表完成大量工作。 我们尽可能优化 PowerShell,但该过程仍需要 12 分钟才能完成。 该模块已包含大量 C# 样式 PowerShell。 这使得转换为二进制模块干净且简单。 通过转换为二进制模块,我们将处理时间从 12 分钟缩短到 4 分钟以下。
混合模块
可以将二进制 cmdlet 与 PowerShell 高级函数混合使用。 有关脚本模块的一切内容都同样适用。 包含空 psm1
文件,以便稍后可以添加其他 PowerShell 函数。
我已创建的几乎所有已编译 cmdlet 一开始都是 PowerShell 函数。 我们的所有二进制模块都是真正的混合模块。
生成脚本
我在这里保持了构建脚本的简洁。 我通常使用一个大型 Invoke-Build
脚本作为 CI/CD 流水线的一部分。 它执行更神奇的操作,例如运行 Pester 测试、运行 PSScriptAnalyzer、管理版本以及发布到 PSGallery。 一旦开始为模块使用生成脚本,我就能找到要添加到其中的许多内容。
规划模块
本模块的计划是为 C# 代码创建一个 src
文件夹,并像为脚本模块一样构建其余部分。 这包括使用生成脚本将所有内容编译到 Output
文件夹中。 文件夹结构如下所示:
MyModule
├───src
├───Output
│ └───MyModule
├───MyModule
│ ├───Data
│ ├───Private
│ └───Public
└───Tests
入门
首先,我需要创建文件夹并创建 git 存储库。 我使用 $module
作为模块名称的占位符。 这应该使你能够更轻松地根据需要重复使用这些示例。
$module = 'MyModule'
New-Item -Path $module -Type Directory
Set-Location $module
git init
然后创建根级别文件夹。
New-Item -Path 'src' -Type Directory
New-Item -Path 'Output' -Type Directory
New-Item -Path 'Tests' -Type Directory
New-Item -Path $module -Type Directory
二进制模块设置
本文重点介绍二进制模块,以便从中开始。 本部分从 创建跨平台二进制模块 指南中提取示例。 如果需要更多详细信息或存在任何问题,请查看该指南。
首先,我们需要检查已安装 dotnet core SDK 的版本。 我使用的是 2.1.4,但在继续之前,你应该拥有 2.0.0 或更高版本。
PS> dotnet --version
2.1.4
我正在为这一部分做出 src
文件夹。
Set-Location 'src'
使用 dotnet 命令创建新的类库。
dotnet new classlib --name $module
这在子文件夹中创建了库项目,但我不想要这种多余的嵌套层级。 我要将这些文件提升一个级别。
Move-Item -Path .\$module\* -Destination .\
Remove-Item $module -Recurse
设置项目的 .NET Core SDK 版本。 我有 2.1 SDK,因此我要指定 2.1.0
。
如果使用 2.0 SDK,请使用 2.0.0
。
dotnet new globaljson --sdk-version 2.1.0
将 PowerShell 标准库NuGet 包 添加到项目中。 请确保使用最新版本,以达到所需的兼容性级别。 我默认为最新版本,但我认为此模块不会利用 PowerShell 3.0 以外的任何功能。
dotnet add package PowerShellStandard.Library --version 7.0.0-preview.1
我们应有如下所示的 src 文件夹:
PS> Get-ChildItem
Directory: \MyModule\src
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 7/14/2018 9:51 PM obj
-a---- 7/14/2018 9:51 PM 86 Class1.cs
-a---- 7/14/2018 10:03 PM 259 MyModule.csproj
-a---- 7/14/2018 10:05 PM 45 global.json
现在,我们已准备好将自己的代码添加到项目中。
构建二进制 cmdlet
我们需要更新 src\Class1.cs
以包含此入门命令行小程序:
using System;
using System.Management.Automation;
namespace MyModule
{
[Cmdlet( VerbsDiagnostic.Resolve , "MyCmdlet")]
public class ResolveMyCmdletCommand : PSCmdlet
{
[Parameter(Position=0)]
public Object InputObject { get; set; }
protected override void EndProcessing()
{
this.WriteObject(this.InputObject);
base.EndProcessing();
}
}
}
重命名文件以匹配类名。
Rename-Item .\Class1.cs .\ResolveMyCmdletCommand.cs
然后,我们可以生成模块。
PS> dotnet build
Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Restore completed in 18.19 ms for C:\workspace\MyModule\src\MyModule.csproj.
MyModule -> C:\workspace\MyModule\src\bin\Debug\netstandard2.0\MyModule.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:02.19
我们可以在新 dll 上调用 Import-Module
来加载新的 cmdlet。
PS> Import-Module .\bin\Debug\netstandard2.0\$module.dll
PS> Get-Command -Module $module
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Resolve-MyCmdlet 1.0.0.0 MyModule
如果系统上的导入失败,请尝试将 .NET 更新到 4.7.1 或更高版本。 创建跨平台二进制模块 指南详细介绍了旧版 .NET 的 .NET 支持和兼容性。
模块清单
我们可以导入 dll 并具有工作模块,这很酷。 我想继续使用它并创建一个模块清单。 如果稍后要发布到 PSGallery,我们需要该清单。
可以从项目的根目录运行此命令来创建所需的模块清单。
$manifestSplat = @{
Path = ".\$module\$module.psd1"
Author = 'Kevin Marquette'
NestedModules = @('bin\MyModule.dll')
RootModule = "$module.psm1"
FunctionsToExport = @('Resolve-MyCmdlet')
}
New-ModuleManifest @manifestSplat
我还将为未来的 PowerShell 函数创建一个空根模块。
Set-Content -Value '' -Path ".\$module\$module.psm1"
这样,我就可以在同一项目中混合普通 PowerShell 函数和二进制 cmdlet。
生成完整模块
我将所有内容一起编译为输出文件夹。 我们需要创建一个生成脚本来执行此作。 我通常会将此内容添加到 Invoke-Build
脚本,但我们可以为此示例保持简单。 将它添加到项目根目录中的 build.ps1
。
$module = 'MyModule'
Push-Location $PSScriptRoot
dotnet build $PSScriptRoot\src -o $PSScriptRoot\output\$module\bin
Copy-Item "$PSScriptRoot\$module\*" "$PSScriptRoot\output\$module" -Recurse -Force
Import-Module "$PSScriptRoot\Output\$module\$module.psd1"
Invoke-Pester "$PSScriptRoot\Tests"
这些命令生成 DLL 并将其放入 output\$module\bin
文件夹中。 然后,它将其他模块文件复制到原地。
Output
└───MyModule
├───MyModule.psd1
├───MyModule.psm1
└───bin
├───MyModule.deps.json
├───MyModule.dll
└───MyModule.pdb
此时,可以使用 psd1 文件导入模块。
Import-Module ".\Output\$module\$module.psd1"
在这里,我们可以将 .\Output\$module
文件夹拖放到 $Env:PSModulePath
目录中,每当我们需要它时,它会自动加载命令。
更新:dotnet new PSModule
我了解到,dotnet
工具具有 PSModule
模板。
我上面概述的所有步骤仍然有效,但此模板省去了其中的许多步骤。它仍然是一个比较新的模板,目前正在进行一些改进。 期待它从现在起不断变得更好。
安装和使用 PSModule 模板的方法如下。
dotnet new -i Microsoft.PowerShell.Standard.Module.Template
dotnet new psmodule
dotnet build
Import-Module "bin\Debug\netstandard2.0\$module.dll"
Get-Module $module
此最小可行模板负责添加 .NET SDK、PowerShell 标准库,并在项目中创建一个示例类。 可以立即生成并运行它。
重要详细信息
在结束本文之前,下面是值得一提的一些其他详细信息。
卸载DLL文件
加载二进制模块后,无法真正卸载它。 DLL 文件被锁定,直到卸载它。 这在开发时可能会很烦人,因为每次进行更改并想要生成它时,文件通常会被锁定。 解决此问题的唯一可靠方法是关闭加载 DLL 的 PowerShell 会话。
VS Code 重新加载窗口操作
我的大部分 PowerShell 开发工作是在 VS Code中完成的。 当我使用二进制模块(或包含类的模块)时,我已习惯每次生成时重新加载 VS Code。
按 Ctrl+Shift+P 会弹出命令窗口,Reload Window
始终在列表顶部。
嵌套的 PowerShell 会话
另一种选择是具有良好的 Pester 测试覆盖率。 然后,可以调整 build.ps1 脚本以启动新的 PowerShell 会话、执行生成、运行测试并关闭会话。
更新已安装的模块
尝试更新本地安装的模块时,此锁定可能会令人恼火。 如果有任何会话加载了它,则必须找到并关闭它。 从 PSGallery 进行安装时,这不太存在问题,因为模块版本控制会将新版本放在不同的文件夹中。
你可以设置一个本地的 PSGallery,并在构建过程中向其发布内容。 然后从该 PSGallery 进行本地安装。 这听起来像是很多工作,但这可以像启动 docker 容器一样简单。 我在 的帖子“在 PSRepository中使用 NuGet 服务器”中介绍了一种实现该方法的方法。
最后的想法
我没有涉及用于创建 cmdlet 的 C# 语法,不过在 Windows PowerShell SDK中有大量相关文档。 作为深入了解 C# 的跳板,这绝对是值得尝试了解的内容。