PowerShell 技能连载 - 包管理与依赖控制

适用于 PowerShell 5.1 及以上版本

PowerShell 模块生态在过去几年里蓬勃发展,PowerShell Gallery 上已经托管了数以万计的模块。从日常运维的 Active Directory 管理,到云端自动化的 Az 模块,再到新兴的 AI 交互工具,几乎每种场景都有对应的模块可用。然而,模块数量的增长也带来了管理上的挑战:不同项目依赖同一个模块的不同版本、私有环境的离线分发需求、以及供应链安全对模块来源的审计要求,都是实际工作中必须面对的问题。

PowerShell 7 引入了 PSResourceGet(Microsoft.PowerShell.PSResourceGet)作为新一代包管理引擎,替代了沿用多年的 PowerShellGet v2。PSResourceGet 基于 NuGet 协议重新实现了仓库交互,在性能、安全性和功能覆盖面上都有显著提升。同时,它保留了与 PowerShellGet 类似的命令风格,降低了迁移成本。对于仍然运行在 Windows PowerShell 5.1 环境中的系统,PowerShellGet 依然可用,但强烈建议尽早迁移到 PSResourceGet。

本文将从基础操作入手,逐步介绍模块搜索与安装、版本锁定与依赖分析,以及私有仓库的搭建方法,帮助你在不同规模的自动化环境中实现可靠的包管理与依赖控制。

PSResourceGet 基础操作

PSResourceGet 的核心操作围绕”仓库(Repository)”展开。仓库是模块的存储和分发端点,默认连接到 PowerShell Gallery。下面的代码展示了从注册仓库到搜索、安装、更新模块的完整流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装 PSResourceGet 模块(如果尚未安装)
Install-Module Microsoft.PowerShell.PSResourceGet -Force -Scope CurrentUser

# 查看已注册的仓库
Get-PSResourceRepository

# 注册额外的仓库,例如自定义的 NuGet 源
Register-PSResourceRepository -Name 'MyCompanyFeed' -Uri 'https://nuget.mycompany.com/v3/index.json'

# 搜索模块:在所有仓库中查找包含 "Az" 关键字的模块
Find-PSResource -Name '*Az*' -Type Module -Repository 'PSGallery' | Select-Object Name, Version, Description | Format-Table -AutoSize

# 安装指定模块的最新稳定版本
Install-PSResource -Name 'Pester' -Scope CurrentUser -TrustRepository

# 安装指定版本的模块
Install-PSResource -Name 'Pester' -Version '5.5.0' -Scope CurrentUser

# 更新已安装的模块到最新版本
Update-PSResource -Name 'Pester' -Scope CurrentUser

# 查看本地已安装的模块信息
Get-InstalledPSResource -Name 'Pester'

上述代码中,Register-PSResourceRepository 用于注册自定义仓库,Find-PSResource 支持通配符搜索并可以限定仓库范围,Install-PSResourceUpdate-PSResource 分别完成安装与升级操作。-TrustRepository 参数表示信任该仓库,避免每次安装时都弹出确认提示。

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Name            Uri                                     Trusted
---- --- -------
PSGallery https://www.powershellgallery.com/api/… False
MyCompanyFeed https://nuget.mycompany.com/v3/indexTrue

Name Version Description
---- ------- -----------
Az 12.0.0 Microsoft Azure PowerShell
Az.Accounts 3.0.0 Microsoft Azure PowerShell - Accounts
Az.Compute 7.0.0 Microsoft Azure PowerShell - Compute

Name Version Prerelease Repository Description
---- ------- ---------- ---------- -----------
Pester 5.7.1 PSGallery Pester is the ubiquitous test…

版本锁定与依赖管理

在自动化管道和多人协作的项目中,模块版本的一致性至关重要。不同开发者或不同环境如果安装了不兼容的模块版本,可能导致脚本行为不一致甚至运行失败。PSResourceGet 提供了多种版本控制机制来应对这一问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 安装精确版本(版本锁定)
Install-PSResource -Name 'Az.Accounts' -Version '3.0.0' -Scope CurrentUser

# 安装版本范围内的最新版本(支持 NuGet 版本范围语法)
# [3.0.0, 4.0.0) 表示大于等于 3.0.0 且小于 4.0.0
Install-PSResource -Name 'Az.Accounts' -Version '[3.0.0, 4.0.0)' -Scope CurrentUser

# 安装预发布版本
Install-PSResource -Name 'Pester' -Version '6.0.0' -Prerelease -Scope CurrentUser

# 查看模块的依赖关系
$resource = Find-PSResource -Name 'Az.Compute' -Repository 'PSGallery'
$resource.Dependencies | Format-Table Name, VersionRange -AutoSize

# 导出当前环境的模块清单(用于版本锁定文件)
$installed = Get-InstalledPSResource
$lockData = $installed | ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Version = $_.Version
}
}
$lockData | ConvertTo-Json -Depth 3 | Set-Content -Path './modules.lock.json' -Encoding UTF8

# 根据锁定文件批量恢复模块
$lock = Get-Content -Path './modules.lock.json' -Raw | ConvertFrom-Json
foreach ($entry in $lock) {
Install-PSResource -Name $entry.Name -Version $entry.Version -Scope CurrentUser -ErrorAction SilentlyContinue
}

这段代码展示了几个关键的版本管理技巧:使用 RequiredVersion 精确锁定版本,使用 NuGet 版本范围语法控制兼容范围,通过 Dependencies 属性分析模块的依赖树,以及将环境状态导出为 JSON 锁定文件以便在另一台机器上复现。

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Name       VersionRange
---- ------------
Az.Accounts [3.0.0, 4.0.0)

modules.lock.json 内容示例:
[
{
"Name": "Pester",
"Version": "5.7.1"
},
{
"Name": "Az.Accounts",
"Version": "3.0.0"
},
{
"Name": "Az.Compute",
"Version": "7.0.0"
}
]

私有仓库搭建与内网分发

在企业环境中,出于安全审计或网络隔离的需要,通常不希望服务器直接访问公网的 PowerShell Gallery。搭建私有仓库可以解决这个问题,同时也能用于分发团队内部开发的模块。NuGet.Server 是一种轻量级的私有仓库方案,配合 PSResourceGet 可以实现完整的内网分发流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# ========== 服务端:搭建 NuGet.Server ==========

# 安装 NuGet.Server 包(在一台有 IIS 的 Windows 服务器上执行)
# 首先确保 NuGet 包源已注册
Register-PackageSource -Name 'NuGetGallery' -Location 'https://www.nuget.org/api/v2' -ProviderName NuGet -Trusted

# 创建 NuGet.Server 站点的目录
$sitePath = 'C:\PSGallery\Private'
New-Item -ItemType Directory -Path $sitePath -Force

# 使用 dotnet 方式创建 NuGet.Server(需要 .NET SDK)
# 或者直接下载 NuGet.Server 包并解压
Save-Package -Name NuGet.Server -Source 'NuGetGallery' -Path $sitePath

# 将自定义模块打包为 nupkg 并发布到私有仓库
$modulePath = 'C:\Modules\MyUtils'
$nupkgOutput = 'C:\PSGallery\Packages'

# 使用 PSResourceGet 发布模块
Publish-PSResource -Path $modulePath -Repository 'MyCompanyFeed' -ApiKey 'your-api-key-here'

# ========== 客户端:从私有仓库安装 ==========

# 在客户端机器上注册私有仓库
Register-PSResourceRepository -Name 'CompanyGallery' -Uri 'https://psgallery.mycompany.com/v3/index.json' -Trusted

# 设置默认仓库优先级(私有仓库优先于公网)
Set-PSResourceRepository -Name 'CompanyGallery' -Priority 1

# 从私有仓库搜索和安装模块
Find-PSResource -Name 'MyUtils' -Repository 'CompanyGallery'
Install-PSResource -Name 'MyUtils' -Repository 'CompanyGallery' -Scope CurrentUser

# 验证模块来源
Get-InstalledPSResource -Name 'MyUtils' | Select-Object Name, Version, Repository

上述代码分为服务端和客户端两部分。服务端负责搭建 NuGet.Server 并发布内部模块,客户端则注册私有仓库、设置优先级,并从中安装模块。通过设置 Priority 参数,可以确保私有仓库中的模块优先于公网同名模块被使用,这在覆盖公网模块的场景中非常有用。

执行结果示例:

1
2
3
4
5
6
7
Name    Version Repository       Description
---- ------- ---------- -----------
MyUtils 1.2.0 CompanyGallery Company internal utility module…

Name Version Repository
---- ------- ----------
MyUtils 1.2.0 CompanyGallery

注意事项

  1. PSResourceGet 与 PowerShellGet 的兼容性:PSResourceGet 可以管理由 PowerShellGet v2/v3 安装的模块,但反向操作可能存在兼容性问题。建议在团队内统一使用 PSResourceGet,避免混用导致的版本记录混乱。

  2. 版本范围语法:PSResourceGet 使用 NuGet 版本范围语法,方括号表示包含边界、圆括号表示排除边界。例如 [1.0.0, 2.0.0) 表示大于等于 1.0.0 且小于 2.0.0。务必仔细检查范围表达式,避免因语法错误导致意外安装了不兼容的版本。

  3. 模块锁定文件应纳入版本控制modules.lock.json 类似于 npm 的 package-lock.json,应与项目代码一起提交到 Git 仓库。这样可以确保 CI/CD 管道中的模块版本与开发环境完全一致。

  4. 私有仓库的安全加固:生产环境的私有仓库应启用 HTTPS、配置 API Key 认证,并定期审计已发布的模块内容。对于高安全要求的环境,可以考虑使用 Azure Artifacts 或 JFrog Artifactory 等企业级制品仓库。

  5. 离线环境的模块分发:对于完全隔离的网络环境,可以使用 Save-PSResource 将模块及其依赖下载到本地目录,然后通过 U 盘或内部文件共享拷贝到目标机器,再用 Install-PSResource 从本地路径安装。这种方式不需要搭建完整的 NuGet 服务。

  6. 模块依赖冲突排查:当遇到模块加载冲突时,可以使用 Get-Module -ListAvailable 查看所有可用版本,结合 $env:PSModulePath 分析模块搜索路径的优先级。对于冲突严重的环境,考虑使用 PowerShell 的 Assembly Load Context(ALC)隔离机制,或在不同项目中使用独立的模块安装路径。

PowerShell 技能连载 - PowerShell Gallery 发布

适用于 PowerShell 5.1 及以上版本

PowerShell Gallery(www.powershellgallery.com)是微软官方维护的 PowerShell 模块和脚本共享平台,类似于 Node.js 的 npm 或 Python 的 PyPI。通过它,你可以将自己编写的模块分发给全球的 PowerShell 用户,也可以方便地安装其他人发布的工具。对于团队协作而言,将内部通用组件发布到私有 Gallery 或公开 Gallery,能够显著提升代码复用率和团队协作效率。

本文将从模块准备、API Key 管理、发布流程以及版本更新四个环节,完整介绍如何将一个自定义 PowerShell 模块发布到 PowerShell Gallery。无论你是开源项目维护者还是企业内部工具开发者,掌握这一流程都能让你的 PowerShell 代码分发更加规范和专业。

准备模块清单文件

PowerShell Gallery 要求发布的模块必须包含模块清单文件(.psd1)。清单文件中定义了模块的元数据,包括版本号、作者、描述、依赖项等信息。其中一些字段是 Gallery 发布的必填项,缺少这些字段会导致发布失败。

使用 New-ModuleManifest 可以快速创建一个标准的清单文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 创建模块目录结构
$modulePath = Join-Path -Path $env:USERPROFILE -ChildPath "Documents\PowerShell\Modules\MyUtils"
if (-not (Test-Path -Path $modulePath)) {
New-Item -Path $modulePath -ItemType Directory -Force | Out-Null
Write-Host "已创建模块目录: $modulePath"
}

# 创建模块清单
$manifestParams = @{
Path = Join-Path -Path $modulePath -ChildPath "MyUtils.psd1"
RootModule = "MyUtils.psm1"
ModuleVersion = "1.0.0"
Author = "Your Name"
CompanyName = "Your Company"
Description = "A collection of utility functions for daily PowerShell tasks"
PowerShellVersion = "5.1"
FunctionsToExport = @("Get-SystemReport", "Invoke-HealthCheck")
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
Tags = @("Utility", "Automation", "Monitoring")
ProjectUri = "https://github.com/yourname/MyUtils"
LicenseUri = "https://github.com/yourname/MyUtils/blob/main/LICENSE"
ReleaseNotes = "Initial release with system report and health check functions."
}

New-ModuleManifest @manifestParams
Write-Host "模块清单已创建"

执行结果示例:

1
2
已创建模块目录: C:\Users\admin\Documents\PowerShell\Modules\MyUtils
模块清单已创建

清单中 TagsProjectUriLicenseUriReleaseNotes 这几个字段虽然不是 New-ModuleManifest 的必需参数,但 PowerShell Gallery 发布时会检查它们。缺失这些字段虽然不会阻止发布,但会严重影响模块在 Gallery 中的可发现性和用户体验。建议在发布前使用 Test-ModuleManifest 验证清单文件的完整性。

获取并配置 API Key

发布模块到 PowerShell Gallery 需要一个 API Key。你需要先在 powershellgallery.com 注册账户(支持 Microsoft 账户或 GitHub 账户登录),然后在账户设置中生成 API Key。API Key 本质上是一个 NuGet API Key,PowerShell Gallery 底层基于 NuGet 协议运行。

为了安全起见,不要将 API Key 硬编码在脚本中。推荐的做法是将 Key 保存到环境变量或凭据管理器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 检查是否已配置 API Key 环境变量
if ($env:PSGALLERY_API_KEY) {
Write-Host "API Key 已配置,长度: $($env:PSGALLERY_API_KEY.Length) 个字符"
}
else {
Write-Host "尚未配置 PSGALLERY_API_KEY 环境变量"
Write-Host "请按以下步骤操作:"
Write-Host " 1. 访问 https://www.powershellgallery.com/users/account/LogOn"
Write-Host " 2. 登录后进入 API Keys 页面"
Write-Host " 3. 点击 Create 生成新的 API Key"
Write-Host " 4. 将 Key 保存为环境变量:"
Write-Host ' [Environment]::SetEnvironmentVariable("PSGALLERY_API_KEY", "your-key-here", "User")'
}

# 永久保存 API Key 到用户级环境变量(只需执行一次)
# [Environment]::SetEnvironmentVariable("PSGALLERY_API_KEY", "your-api-key", "User")

执行结果示例:

1
2
3
4
5
6
7
尚未配置 PSGALLERY_API_KEY 环境变量
请按以下步骤操作:
1. 访问 https://www.powershellgallery.com/users/account/LogOn
2. 登录后进入 API Keys 页面
3. 点击 Create 生成新的 API Key
4. 将 Key 保存为环境变量:
[Environment]::SetEnvironmentVariable("PSGALLERY_API_KEY", "your-key-here", "User")

API Key 生成时可以选择过期时间和作用域。建议使用最小权限原则:如果只需要推送特定模块,就创建限定到该模块名称的 Key,而不是创建全权限 Key。这样即使 Key 泄露,影响范围也可以控制。

一切准备就绪后,使用 Publish-Module 命令将模块发布到 PowerShell Gallery。发布前务必在本地完整测试模块功能,因为一旦发布,版本号就不可重复使用——如果你发布了 1.0.0 版本后发现 bug,只能通过发布 1.0.1 等新版本修复,无法覆盖 1.0.0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 发布前的预检查
$modulePath = Join-Path -Path $env:USERPROFILE -ChildPath "Documents\PowerShell\Modules\MyUtils"

# 验证清单文件
$manifest = Test-ModuleManifest -Path (Join-Path -Path $modulePath -ChildPath "MyUtils.psd1")
if ($manifest) {
Write-Host "模块名称: $($manifest.Name)"
Write-Host "版本号: $($manifest.Version)"
Write-Host "导出函数: $($manifest.ExportedFunctions.Keys -join ', ')"
}

# 发布模块
$publishParams = @{
Path = $modulePath
NuGetApiKey = $env:PSGALLERY_API_KEY
Repository = "PSGallery"
Verbose = $true
}

Publish-Module @publishParams
Write-Host "模块已成功发布到 PowerShell Gallery!"

执行结果示例:

1
2
3
4
5
6
7
模块名称: MyUtils
版本号: 1.0.0
导出函数: Get-SystemReport, Invoke-HealthCheck
VERBOSE: Successfully created an API key for PSGallery.
VERBOSE: Attempting to publish module 'MyUtils' version '1.0.0' to 'PSGallery'.
VERBOSE: Successfully published module 'MyUtils' version '1.0.0' to 'PSGallery'.
模块已成功发布到 PowerShell Gallery!

发布成功后,模块通常在几分钟内就能在 PowerShell Gallery 上搜索到。其他用户可以通过 Install-Module -Name MyUtils 直接安装使用。

验证发布结果

发布完成后,应当验证模块在 Gallery 上的展示效果。可以通过命令行查询,也可以在浏览器中直接访问模块页面,检查描述、标签、项目链接等信息是否正确显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 通过命令行验证模块是否已上线
$published = Find-Module -Name "MyUtils" -Repository PSGallery

if ($published) {
Write-Host "模块已上线!"
Write-Host " 名称: $($published.Name)"
Write-Host " 版本: $($published.Version)"
Write-Host " 描述: $($published.Description)"
Write-Host " 作者: $($published.Author)"
Write-Host " 下载量: $($published.AdditionalMetadata['versionDownloadCount'])"
Write-Host " 页面: https://www.powershellgallery.com/packages/$($published.Name)/$($published.Version)"
}
else {
Write-Host "警告: 未在 Gallery 中找到模块,可能需要等待几分钟"
}

执行结果示例:

1
2
3
4
5
6
7
模块已上线!
名称: MyUtils
版本: 1.0.0
描述: A collection of utility functions for daily PowerShell tasks
作者: Your Name
下载量: 0
页面: https://www.powershellgallery.com/packages/MyUtils/1.0.0

如果信息有误,可以在修正后发布新版本。已发布的版本无法修改或删除(除非联系 PowerShell Gallery 运维团队)。

更新模块版本

当模块修复了 bug 或新增功能后,需要递增版本号并重新发布。PowerShell Gallery 遵循语义化版本(SemVer)规则:主版本号.次版本号.修订号。例如,修复 bug 递增修订号(1.0.0 → 1.0.1),新增功能递增次版本号(1.0.1 → 1.1.0),不兼容的 API 变更递增主版本号(1.1.0 → 2.0.0)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 更新模块版本号和发布说明
$modulePath = Join-Path -Path $env:USERPROFILE -ChildPath "Documents\PowerShell\Modules\MyUtils"
$manifestPath = Join-Path -Path $modulePath -ChildPath "MyUtils.psd1"

# 读取当前版本并递增修订号
$currentManifest = Test-ModuleManifest -Path $manifestPath
$currentVersion = $currentManifest.Version
$newVersion = [version]::new(
$currentVersion.Major,
$currentVersion.Minor,
$currentVersion.Build + 1
)

Write-Host "当前版本: $currentVersion → 新版本: $newVersion"

# 更新清单中的版本号和发布说明
$updateData = @{
ModuleVersion = $newVersion.ToString()
ReleaseNotes = "Fixed edge case in Get-SystemReport when WMI service is stopped."
}
Update-ModuleManifest -Path $manifestPath @updateData

# 重新发布
Publish-Module -Path $modulePath -NuGetApiKey $env:PSGALLERY_API_KEY -Repository PSGallery
Write-Host "新版本 $newVersion 已发布"

执行结果示例:

1
2
3
4
当前版本: 1.0.0 → 新版本: 1.0.1
VERBOSE: Attempting to publish module 'MyUtils' version '1.0.1' to 'PSGallery'.
VERBOSE: Successfully published module 'MyUtils' version '1.0.1' to 'PSGallery'.
新版本 1.0.1 已发布

版本更新后,已安装旧版本的用户通过 Update-Module -Name MyUtils 即可获取最新版本。PowerShellGet 会自动处理版本比较和下载安装。

自动化发布流程

在 CI/CD 场景中,可以将发布流程集成到 GitHub Actions 或 Azure Pipelines 中,实现提交代码后自动发布新版本。以下是一个使用 PowerShell 脚本在 CI 环境中发布的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 适用于 CI/CD 管道的发布脚本
# 从 git tag 中提取版本号,确保版本号与代码提交严格对应
$gitTag = git describe --tags --abbrev=0 2>$null
if (-not $gitTag) {
Write-Error "未找到 git tag,请先创建版本标签"
return
}

# 从 tag 名称中解析版本号(例如 v1.2.3 → 1.2.3)
$versionStr = $gitTag -replace '^v', ""
$newVersion = [version]$versionStr

Write-Host "准备发布版本: $newVersion (from tag: $gitTag)"

# 更新清单
$manifestPath = "./src/MyUtils/MyUtils.psd1"
$releaseNotes = git log --pretty=format:"- %s" ("$($gitTag)~1"..HEAD) 2>$null
if (-not $releaseNotes) {
$releaseNotes = "Release $newVersion"
}

Update-ModuleManifest -Path $manifestPath -ModuleVersion $newVersion.ToString() -ReleaseNotes $releaseNotes

# 验证清单完整性
$requiredFields = @("Description", "Author", "Tags", "ProjectUri", "LicenseUri")
$manifest = Test-ModuleManifest -Path $manifestPath
$warnings = @()
foreach ($field in $requiredFields) {
$value = $manifest.$field
if (-not $value) {
$warnings += "缺少必填字段: $field"
}
}

if ($warnings.Count -gt 0) {
Write-Host "发布前检查警告:"
foreach ($w in $warnings) {
Write-Host " [WARN] $w"
}
}

# 发布
Publish-Module -Path "./src/MyUtils" -NuGetApiKey $env:PSGALLERY_API_KEY -Repository PSGallery
Write-Host "CI/CD 发布完成: v$newVersion"

执行结果示例:

1
2
准备发布版本: 1.1.0 (from tag: v1.1.0)
CI/CD 发布完成: v1.1.0

将 API Key 存储在 CI/CD 平台的安全变量中(如 GitHub Secrets),确保密钥不会出现在日志和代码仓库中。这种方式适合团队协作开发,每次合并代码后自动触发构建和发布。

注意事项

  • 版本号不可复用:PowerShell Gallery 不允许删除已发布的版本,也无法覆盖已有版本号。即使你发布了一个有严重 bug 的版本,该版本号也永久被占用。发布前务必在本地充分测试,确认无误后再推送。
  • 清单必填字段DescriptionAuthor 是 Gallery 的强制要求字段。虽然 TagsProjectUriLicenseUri 不阻止发布,但缺失这些字段会让模块难以被搜索和信任。建议在 New-ModuleManifest 阶段就完整填写所有字段。
  • API Key 安全管理:不要将 API Key 提交到代码仓库,也不要在聊天记录或日志中明文输出。使用环境变量或 CI/CD 平台的安全变量存储,并为不同用途创建不同作用域的 Key。
  • 模块大小限制:PowerShell Gallery 对单个模块包的大小有限制(目前约 250 MB)。如果你的模块包含大量二进制文件或数据文件,考虑使用外部存储并仅在模块中提供下载脚本。
  • 私有 Gallery 部署:企业内部可以使用 PowerShell Gallery 的私有实例(如基于 NuGet Server 或 Artifactory 搭建),发布流程完全相同,只需通过 Register-PSRepository 注册内部仓库地址,然后在 Publish-Module 中指定 -Repository 参数即可。
  • 依赖项声明:如果模块依赖其他模块,务必在清单中通过 RequiredModules 字段声明。这样用户安装你的模块时,PowerShellGet 会自动解析并安装所有依赖,避免因缺少依赖导致模块加载失败。

PowerShell 技能连载 - PowerShellGet 包管理

适用于 PowerShell 5.1 及以上版本

背景

在现代运维和开发工作中,模块化管理代码已成为主流趋势。Python 有 pip,Node.js 有 npm,而 PowerShell 的官方包管理工具就是 PowerShellGet。它提供了一组标准 cmdlet,让用户可以从 PowerShell Gallery 或私有 NuGet 源搜索、安装、更新和发布模块及脚本。自 PowerShell 5.0 起,PowerShellGet 已内置于系统中,开箱即用。

在日常工作中,我们经常面临以下场景:团队共享了一批自定义运维模块,需要统一分发和版本控制;某个第三方模块发布了安全补丁,需要快速检查并升级所有服务器上的旧版本;或者我们需要将内部开发的工具模块发布到私有仓库,供多个环境使用。PowerShellGet 正是解决这些问题的利器。

本文将从仓库管理、模块搜索与安装、批量更新、以及私有源搭建等方面,全面介绍 PowerShellGet 的实际用法,帮助你构建高效的 PowerShell 模块管理工作流。

查看和管理仓库源

PowerShellGet 依赖 NuGet 协议与仓库交互。默认情况下,系统已注册 PowerShell Gallery 作为公共源。我们可以先查看当前注册的所有仓库,确认源是否可用。

1
2
# 查看已注册的仓库列表
Get-PSRepository
1
2
3
Name                      InstallationPolicy   SourceLocation
---- ----------------- --------------
PSGallery Untrusted https://www.powershellgallery.com/api/v2

如果默认的 PSGallery 仓库丢失(某些精简系统可能出现),可以使用以下命令恢复:

1
2
3
4
5
# 恢复默认的 PSGallery 仓库
Register-PSRepository -Default

# 验证仓库是否恢复成功
Get-PSRepository
1
2
3
Name                      InstallationPolicy   SourceLocation
---- ----------------- --------------
PSGallery Untrusted https://www.powershellgallery.com/api/v2

在生产环境中,建议将仓库的 InstallationPolicy 设为 Trusted,避免每次安装时都需要手动确认:

1
2
# 将 PSGallery 设为受信任源
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted

搜索和安装模块

PowerShellGet 提供了 Find-ModuleInstall-Module 两个核心 cmdlet,分别用于搜索和安装模块。下面演示如何按关键词搜索模块,并查看模块的详细信息。

1
2
3
4
5
6
# 按标签搜索安全相关的模块
$modules = Find-Module -Tag 'Security' | Select-Object -First 5

foreach ($m in $modules) {
Write-Output ("模块名: {0}, 版本: {1}, 描述: {2}" -f $m.Name, $m.Version, $m.Description)
}
1
2
3
4
5
模块名: Carbon, 版本: 2.5.0, 描述: Carbon is a PowerShell module for automating the configuration of Windows.
模块名: ACMESharp, 版本: 0.8.1, 描述: Client library for the ACME protocol for certificate management.
模块名: DSInternals, 版本: 2.22, 描述: The DSInternals PowerShell Module exposes several internal features of Active Directory.
模块名: DSCEA, 版本: 1.2.0.0, 描述: DSCEA is a scanning engine for processing Test-DscConfiguration results.
模块名: PoshACME, 版本: 3.10.0, 描述: PowerShell module for ACME certificate management.

找到需要的模块后,使用 Install-Module 进行安装。建议指定 -Scope CurrentUser 避免需要管理员权限:

1
2
3
4
5
# 安装指定模块到当前用户范围
Install-Module -Name 'PSScriptAnalyzer' -Scope CurrentUser -Repository PSGallery

# 确认安装成功
Get-Module -ListAvailable -Name 'PSScriptAnalyzer' | Select-Object Name, Version, ModuleBase
1
2
3
Name               Version    ModuleBase
---- ------- ----------
PSScriptAnalyzer 1.22.0 C:\Users\user\Documents\PowerShell\Modules\PSScriptAnalyzer\1.22.0

有时候我们需要安装特定版本的模块,比如为了兼容性而回退到旧版本:

1
2
# 查看模块的所有可用版本
Find-Module -Name 'PSScriptAnalyzer' -AllVersions | Select-Object Name, Version | Sort-Object Version -Descending | Select-Object -First 5
1
2
3
4
5
6
7
Name               Version
---- -------
PSScriptAnalyzer 1.22.0
PSScriptAnalyzer 1.21.0
PSScriptAnalyzer 1.20.0
PSScriptAnalyzer 1.19.1
PSScriptAnalyzer 1.19.0
1
2
# 安装指定版本
Install-Module -Name 'PSScriptAnalyzer' -RequiredVersion '1.21.0' -Scope CurrentUser -Force

批量检查和更新模块

在维护多台服务器时,定期检查已安装模块是否有更新是一项重要的运维任务。下面的脚本会扫描所有已安装模块,对比 PowerShell Gallery 上的最新版本,并生成更新报告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 获取所有已安装模块的当前版本和最新版本
$installedModules = Get-InstalledModule
$updateReport = @()

foreach ($mod in $installedModules) {
$latest = Find-Module -Name $mod.Name -Repository PSGallery -ErrorAction SilentlyContinue

if ($latest -and $latest.Version -ne $mod.Version) {
$updateReport += [PSCustomObject]@{
Name = $mod.Name
CurrentVersion = $mod.Version
LatestVersion = $latest.Version
NeedsUpdate = $true
}
}
else {
$updateReport += [PSCustomObject]@{
Name = $mod.Name
CurrentVersion = $mod.Version
LatestVersion = $mod.Version
NeedsUpdate = $false
}
}
}

$updateReport | Format-Table -AutoSize
1
2
3
4
5
6
Name                CurrentVersion  LatestVersion  NeedsUpdate
---- -------------- ------------- -----------
PSScriptAnalyzer 1.21.0 1.22.0 True
PSSqlite 1.1.0 1.1.0 False
PowerShellGet 2.2.5 2.2.5 False
Pester 5.4.0 5.5.0 True

确认需要更新的模块后,可以使用 Update-Module 进行升级:

1
2
3
4
5
6
7
8
# 更新所有有新版本的模块
foreach ($mod in $updateReport) {
if ($mod.NeedsUpdate) {
Write-Output ("正在更新 {0} 从 {1} 到 {2}..." -f $mod.Name, $mod.CurrentVersion, $mod.LatestVersion)
Update-Module -Name $mod.Name -Scope CurrentUser
Write-Output ("{0} 更新完成。" -f $mod.Name)
}
}
1
2
3
4
正在更新 PSScriptAnalyzer 从 1.21.0 到 1.22.0...
PSScriptAnalyzer 更新完成。
正在更新 Pester 从 5.4.0 到 5.5.0...
Pester 更新完成。

注册私有 NuGet 仓库

在企业环境中,公共的 PowerShell Gallery 并不总是可用(比如受限网络环境),或者我们需要分发内部开发的专用模块。这时可以注册私有 NuGet 仓库,实现内部模块的统一管理。

1
2
3
4
5
6
7
8
9
10
11
12
# 注册企业内部的 NuGet 仓库
$repoParams = @{
Name = 'CompanyPSRepo'
SourceLocation = 'https://nuget.company.com/v3/index.json'
PublishLocation = 'https://nuget.company.com/v3/index.json'
InstallationPolicy = 'Trusted'
}

Register-PSRepository @repoParams

# 验证注册结果
Get-PSRepository -Name 'CompanyPSRepo'
1
2
3
Name                InstallationPolicy   SourceLocation
---- ----------------- --------------
CompanyPSRepo Trusted https://nuget.company.com/v3/index.json

注册完成后,就可以像使用 PSGallery 一样搜索和安装私有仓库中的模块:

1
2
3
4
5
# 搜索私有仓库中的模块
Find-Module -Repository 'CompanyPSRepo'

# 从私有仓库安装模块
Install-Module -Name 'Company.Utils' -Repository 'CompanyPSRepo' -Scope CurrentUser
1
2
3
4
5
Version    Name              Repository      Description
------- ---- ---------- -----------
1.5.0 Company.Utils CompanyPSRepo 公司内部通用运维工具模块
2.0.1 Company.Security CompanyPSRepo 公司安全基线检查模块
1.0.0 Company.Logging CompanyPSRepo 统一日志记录模块

发布模块到仓库

开发完一个 PowerShell 模块后,可以使用 Publish-Module 将其发布到 PowerShell Gallery 或私有仓库。发布前需要准备好模块清单(.psd1 文件),确保元数据完整。

1
2
3
# 查看模块清单信息
$modulePath = 'C:\Modules\MyToolKit'
Test-ModuleManifest -Path "$modulePath\MyToolKit.psd1" | Select-Object Name, Version, Author, Description
1
2
3
4
Name        : MyToolKit
Version : 1.0.0
Author : admin
Description : 日常运维工具集
1
2
3
4
5
6
7
8
9
# 发布模块到私有仓库(使用 API Key 认证)
$publishParams = @{
Path = $modulePath
Repository = 'CompanyPSRepo'
NuGetApiKey = 'your-api-key-here'
Verbose = $true
}

Publish-Module @publishParams
1
2
3
4
VERBOSE: 正在准备要发布的模块 'MyToolKit' (版本 1.0.0)...
VERBOSE: 正在创建 NuGet 包...
VERBOSE: 正在将包推送到 'https://nuget.company.com/v3/index.json'...
VERBOSE: 模块 'MyToolKit' (版本 1.0.0) 发布成功。

发布完成后,其他团队成员就可以通过 Find-ModuleInstall-Module 获取最新版本。

注意事项

  1. PowerShellGet 版本差异:PowerShellGet v2(随 Windows 自带)和 v3(需要单独安装)在 cmdlet 命名和参数上有较大变化。v3 的包名为 Microsoft.PowerShell.PSResourceGet,计划在未来替代 v2。迁移前请确认兼容性。

  2. NuGet 提供程序依赖:首次使用 Install-Module 时,系统可能会提示安装 NuGet 提供程序。在企业环境中建议提前部署,可以通过 Install-PackageProvider -Name NuGet -Force 预装。

  3. 执行策略限制:如果系统执行策略设置为 Restricted,模块安装和脚本执行都会被阻止。建议将执行策略设为 RemoteSignedSet-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

  4. 网络代理问题:在需要通过代理访问外网的环境中,PowerShellGet 默认不会自动使用系统代理。需要显式配置 PowerShellGet 的代理设置,或者使用 -Proxy 参数指定代理地址。

  5. 模块冲突处理:如果系统中存在多个版本的同一模块,PowerShell 默认加载路径中排在最前面的版本。使用 Import-Module 时通过 -RequiredVersion 参数明确指定版本,避免意外加载旧版本导致兼容性问题。

  6. 私有仓库安全:发布模块时使用的 API Key 应妥善保管,切勿硬编码在脚本中。建议使用环境变量或 Azure Key Vault 等安全存储来管理凭据,例如 $apiKey = $env:PSGALLERY_API_KEY