PowerShell 技能连载 - PowerShellGet v3 与模块生态

适用于 PowerShell 7.0 及以上版本

PowerShell 的强大很大程度上来自于其丰富的模块生态——从 AWS 和 Azure 的云管理工具,到 Pester 测试框架、Plaster 项目脚手架,社区贡献了成千上万的实用模块。而这一切的基石就是包管理器。PowerShellGet v3(即 PSResourceGet)作为新一代包管理模块,基于 NuGet v3 协议从头重写,带来了显著的性能提升和更现代化的 API 设计。

与 v2 相比,PSResourceGet 的安装和搜索速度快了数倍,支持并行下载,资源类型从单一的 Script 和 Module 扩展到了 DSCResource、Command、RoleCapability 等更细粒度的分类。同时,它对私有仓库(如 Azure Artifacts、JFrog Artifactory、Sonatype Nexus)的原生支持,让企业内部模块的分发和管理变得前所未有的便捷。

本文将从三个层面展开:首先介绍 PSResourceGet 的安装配置与基础模块管理操作,然后深入讲解依赖解析与版本锁定策略,最后演示如何将自研模块发布到公共仓库或私有仓库,并融入 CI/CD 流水线。

PSResourceGet 基础:安装、仓库注册与模块搜索

PSResourceGet 是 PowerShell 7 的官方推荐包管理模块。安装后,你需要注册模块仓库(默认自带 PSGallery),然后就可以搜索、安装和更新模块。理解仓库的优先级机制和信任级别设置,是安全使用包管理器的前提。

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
45
46
# 安装 PSResourceGet 模块
Install-Module -Name PSResourceGet -Force -Scope CurrentUser
Write-Host "PSResourceGet 安装完成"

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

# 注册私有 NuGet 仓库(例如 Azure Artifacts)
$repoParams = @{
Name = 'MyCompanyGallery'
Uri = 'https://pkgs.dev.azure.com/mycompany/_packaging/myFeed/nuget/v3/index.json'
Trusted = $true
Priority = 30
}
Register-PSResourceRepository @repoParams
Write-Host "私有仓库 MyCompanyGallery 注册完成"

# 设置 PSGallery 为受信任(避免每次安装都提示确认)
Set-PSResourceRepository -Name 'PSGallery' -Trusted

# 搜索模块:按关键词查找
Find-PSResource -Name 'Pester' -Repository PSGallery
Write-Host "`n--- 按标签搜索 ---"
Find-PSResource -Tags 'azure' -Repository PSGallery |
Select-Object -First 5 |
Format-Table Name, Version, Description -AutoSize

# 搜索特定类型的资源
Find-PSResource -Type Module -Name 'Az.*' |
Select-Object -First 5 |
Format-Table Name, Version -AutoSize

# 安装模块
Install-PSResource -Name 'Pester' -Version '[5.0.0,6.0.0)' -Repository PSGallery
Write-Host "`nPester 已安装"

# 查看已安装的模块信息
Get-InstalledPSResource -Name 'Pester' |
Format-Table Name, Version, InstalledLocation -AutoSize

# 更新模块到最新版
Update-PSResource -Name 'Pester' -Prerelease
Write-Host "Pester 已更新到最新版本(含预发布版)"

# 卸载模块
# Uninstall-PSResource -Name 'SomeOldModule'

执行结果示例:

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
PSResourceGet 安装完成

Name Uri Trusted Priority
---- --- ------- --------
PSGallery https://www.powershellgallery.com/api/v2 True 50
MyCompanyGallery https://pkgs.dev.azure.com/... True 30
私有仓库 MyCompanyGallery 注册完成

Name Version Description
---- ------- -----------
Pester 5.7.1 Pester provides a framework for running BDD tests

--- 按标签搜索 ---
Name Version Description
---- ------- -----------
Az.Accounts 3.0.2 Microsoft Azure PowerShell...
Az.Compute 7.0.1 Microsoft Azure PowerShell Compute...

Pester 已安装

Name Version InstalledLocation
---- ------- -----------------
Pester 5.7.1 /Users/user/.local/share/powershell/Modules

Pester 已更新到最新版本(含预发布版)

模块依赖与版本管理

随着项目规模增长,模块之间的依赖关系会变得复杂。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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 版本范围语法速查
$versionExamples = @(
@{ Expression = '1.2.3'; Meaning = '精确匹配 1.2.3' }
@{ Expression = '[1.2.3,2.0.0)'; Meaning = '大于等于 1.2.3,小于 2.0.0' }
@{ Expression = '[1.0.0,)'; Meaning = '大于等于 1.0.0(无上限)' }
@{ Expression = '(1.0.0,2.0.0)'; Meaning = '大于 1.0.0,小于 2.0.0(不含边界)' }
@{ Expression = '[1.0.0,2.0.0]'; Meaning = '大于等于 1.0.0,小于等于 2.0.0' }
)
Write-Host "版本范围语法参考:"
$versionExamples | Format-Table -AutoSize

# 使用锁文件管理项目依赖(导出当前环境的模块版本)
$projectModules = @('Pester', 'PSScriptAnalyzer', 'platyPS', 'ModuleBuilder')
$lockData = foreach ($mod in $projectModules) {
$installed = Get-InstalledPSResource -Name $mod -ErrorAction SilentlyContinue
if ($installed) {
[PSCustomObject]@{
ModuleName = $installed.Name
Version = $installed.Version.ToString()
Repository = $installed.Repository
Installed = $installed.InstalledDate.ToString('yyyy-MM-dd')
}
}
else {
[PSCustomObject]@{
ModuleName = $mod
Version = 'NOT INSTALLED'
Repository = '-'
Installed = '-'
}
}
}

$lockFile = Join-Path $PWD 'modules.lock.json'
$lockData | ConvertTo-Json | Set-Content -Path $lockFile -Encoding UTF8
Write-Host "`n依赖锁文件已生成: $lockFile"
$lockData | Format-Table -AutoSize

# 根据锁文件批量安装精确版本
function Install-FromLockFile {
param([string]$Path = 'modules.lock.json')
$lock = Get-Content -Path $Path -Raw | ConvertFrom-Json
foreach ($entry in $lock) {
if ($entry.Version -eq 'NOT INSTALLED') {
Write-Warning "跳过未指定的模块: $($entry.ModuleName)"
continue
}
Write-Host "安装 $($entry.ModuleName) @$($entry.Version)..."
Install-PSResource -Name $entry.ModuleName -Version $entry.Version -Quiet -Reinstall
}
Write-Host "`n所有依赖安装完成"
}

# 检查模块兼容性:确认模块是否支持当前平台
function Test-ModuleCompatibility {
param([string]$ModuleName)
$mod = Find-PSResource -Name $ModuleName -Repository PSGallery -ErrorAction SilentlyContinue
if (-not $mod) {
Write-Warning "未找到模块: $ModuleName"
return
}
$info = [PSCustomObject]@{
ModuleName = $mod.Name
LatestVersion = $mod.Version.ToString()
PowerShell = if ($mod.RequiredResource -match 'Core') { 'PS7+' } else { 'PS5.1+' }
HasPrerelease = $mod.IsPrerelease
}
# 检查是否已安装
$installed = Get-InstalledPSResource -Name $ModuleName -ErrorAction SilentlyContinue
if ($installed) {
$info | Add-Member -NotePropertyName 'InstalledVersion' -NotePropertyValue $installed.Version.ToString()
}
return $info
}

# 批量检查项目依赖的兼容性
foreach ($mod in $projectModules) {
$result = Test-ModuleCompatibility -ModuleName $mod
if ($result) { $result }
}

执行结果示例:

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
版本范围语法参考:
Expression Meaning
---------- -------
1.2.3 精确匹配 1.2.3
[1.2.3,2.0.0) 大于等于 1.2.3,小于 2.0.0
[1.0.0,) 大于等于 1.0.0(无上限)
(1.0.0,2.0.0) 大于 1.0.0,小于 2.0.0(不含边界)
[1.0.0,2.0.0] 大于等于 1.0.0,小于等于 2.0.0

依赖锁文件已生成: /Users/user/project/modules.lock.json

ModuleName Version Repository Installed
---------- ------- ---------- ---------
Pester 5.7.1 PSGallery 2026-03-15
PSScriptAnalyzer 1.23.0 PSGallery 2026-03-15
platyPS 0.14.2 PSGallery 2026-03-10
ModuleBuilder 3.0.0 PSGallery 2026-03-01

安装 Pester @5.7.1...
安装 PSScriptAnalyzer @1.23.0...
安装 platyPS @0.14.2...
安装 ModuleBuilder @3.0.0...
所有依赖安装完成

ModuleName LatestVersion PowerShell HasPrerelease InstalledVersion
---------- ------------- ---------- ------------- ----------------
Pester 5.7.1 PS5.1+ False 5.7.1
PSScriptAnalyzer 1.23.0 PS5.1+ False 1.23.0
platyPS 0.14.2 PS5.1+ False 0.14.2
ModuleBuilder 3.0.0 PS7+ False 3.0.0

模块发布与 CI/CD 集成

当你开发了自己的 PowerShell 模块并希望在团队或社区中共享时,就需要将它发布到模块仓库。PSResourceGet 提供了 Publish-PSResource 命令来简化发布流程。结合 CI/CD 流水线,可以实现代码提交后自动测试、打包和发布的完整工作流。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 准备模块清单(module manifest)
$manifestParams = @{
Path = './MyToolModule/MyToolModule.psd1'
RootModule = 'MyToolModule.psm1'
ModuleVersion = '1.0.0'
Author = 'Vic Hamp'
Description = '企业内部运维工具集模块'
PowerShellVersion = '7.0'
FunctionsToExport = @('Get-SystemReport', 'Start-HealthCheck', 'Reset-ServiceState')
Tags = @('Ops', 'Monitoring', 'HealthCheck', 'Enterprise')
ProjectUri = 'https://github.com/mycompany/MyToolModule'
LicenseUri = 'https://github.com/mycompany/MyToolModule/blob/main/LICENSE'
ReleaseNotes = '初始发布:系统报告、健康检查、服务状态重置功能'
RequiredModules = @(
@{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' }
)
}
New-ModuleManifest @manifestParams
Write-Host "模块清单已创建"

# 验证模块清单有效性
$manifest = Test-ModuleManifest -Path './MyToolModule/MyToolModule.psd1' -ErrorAction Stop
Write-Host "模块名: $($manifest.Name)"
Write-Host "版本: $($manifest.Version)"
Write-Host "导出函数: $($manifest.ExportedFunctions.Keys -join ', ')"

# 发布到 PSGallery(需要 API Key)
function Publish-ModuleToGallery {
param(
[Parameter(Mandatory)]
[string]$ModulePath,
[Parameter(Mandatory)]
[string]$ApiKey
)
$publishParams = @{
Path = $ModulePath
Repository = 'PSGallery'
ApiKey = $ApiKey
Verbose = $true
}
Publish-PSResource @publishParams
Write-Host "模块已发布到 PSGallery"
}

# 发布到私有仓库
function Publish-ModuleToPrivateRepo {
param(
[Parameter(Mandatory)]
[string]$ModulePath,
[Parameter(Mandatory)]
[string]$RepositoryName
)
# 私有仓库通常使用 NuGet API Key 或 PAT 认证
Publish-PSResource -Path $ModulePath -Repository $RepositoryName
Write-Host "模块已发布到私有仓库: $RepositoryName"
}

# CI/CD 集成示例:生成发布用的 GitHub Actions 工作流配置
$githubActions = @{
name = 'Publish PowerShell Module'
on = @{
push = @{ tags = @('v*') }
}
jobs = @{
publish = @{
'runs-on' = 'ubuntu-latest'
steps = @(
@{ uses = 'actions/checkout@v4' }
@{ name = 'Install PowerShell'; uses = 'PowerShell/setup-pwsh@v1' }
@{
name = 'Install PSResourceGet'
run = 'Install-Module -Name PSResourceGet -Force -Scope CurrentUser'
shell = 'pwsh'
}
@{
name = 'Publish Module'
run = @'
$tag = $env:GITHUB_REF -replace ''refs/tags/v'', ''''
Publish-PSResource -Path ./MyToolModule `
-Repository PSGallery `
-ApiKey $env:PSGALLERY_API_KEY
'@
shell = 'pwsh'
env = @{ PSGALLERY_API_KEY = '${{ secrets.PSGALLERY_API_KEY }}' }
}
)
}
}
}

$workflowPath = Join-Path $PWD '.github' 'workflows' 'publish-module.yml'
New-Item -ItemType Directory -Path (Split-Path $workflowPath) -Force | Out-Null
# 使用 ConvertTo-Yaml 需要安装相关模块,这里用 JSON 作为示意
$githubActions | ConvertTo-Json -Depth 10 |
Set-Content -Path $workflowPath -Encoding UTF8
Write-Host "`nGitHub Actions 工作流已生成: $workflowPath"

# 版本号自动递增工具
function Update-ModuleVersion {
param(
[Parameter(Mandatory)]
[string]$ManifestPath,
[ValidateSet('Major', 'Minor', 'Patch')]
[string]$Bump = 'Patch'
)
$content = Get-Content -Path $ManifestPath -Raw
if ($content -match 'ModuleVersion\s*=\s*[''"](\d+)\.(\d+)\.(\d+)[''"]') {
$major = [int]$Matches[1]
$minor = [int]$Matches[2]
$patch = [int]$Matches[3]
switch ($Bump) {
'Major' { $major++; $minor = 0; $patch = 0 }
'Minor' { $minor++; $patch = 0 }
'Patch' { $patch++ }
}
$newVersion = "$major.$minor.$patch"
$content = $content -replace
"(ModuleVersion\s*=\s*[''"])\d+\.\d+\.\d+(['""])",
"`$1$newVersion`$2"
Set-Content -Path $ManifestPath -Value $content -NoNewline
Write-Host "版本号已更新为: $newVersion ($Bump)"
}
else {
Write-Warning "未在清单文件中找到 ModuleVersion"
}
}

Update-ModuleVersion -ManifestPath './MyToolModule/MyToolModule.psd1' -Bump Patch

执行结果示例:

1
2
3
4
5
6
7
模块清单已创建
模块名: MyToolModule
版本: 1.0.0
导出函数: Get-SystemReport, Start-HealthCheck, Reset-ServiceState

GitHub Actions 工作流已生成: /Users/user/project/.github/workflows/publish-module.yml
版本号已更新为: 1.0.1 (Patch)

注意事项

  1. PSResourceGet 与 PowerShellGet v2 的共存问题:PSResourceGet(模块名 Microsoft.PowerShell.PSResourceGet)与旧版 PowerShellGet 可以在同一系统上共存,但不应混用两者的命令来管理同一模块,否则可能导致安装状态不一致。建议在团队中统一选择其中一个,并在项目文档中明确说明。

  2. API Key 安全管理:发布模块到 PSGallery 需要使用 API Key,该密钥应通过环境变量或 CI/CD 平台的 Secrets 机制注入,绝不能硬编码在脚本或配置文件中。PSGallery 的 API Key 可以在账户设置中按权限范围生成,建议遵循最小权限原则。

  3. 私有仓库的认证配置:企业私有仓库(Azure Artifacts、JFrog 等)通常需要 PAT(Personal Access Token)或特定的 NuGet API Key 进行认证。使用 Register-PSResourceRepository 注册仓库时,可以通过 -Credential 参数预设凭据,也可在发布时通过 -ApiKey 参数动态传入。

  4. 版本范围语法要严格:PSResourceGet 使用 NuGet 版本范围语法(方括号和圆括号的组合),与 npm 或 pip 的语义不同。特别是 [1.0.0,2.0.0) 这种左闭右开的区间表达,在锁文件和 CI 脚本中务必仔细核对,否则可能出现依赖版本偏离预期的情况。

  5. 模块结构规范:发布到 PSGallery 的模块必须包含有效的模块清单(.psd1),并且清单中的 FunctionsToExportCmdletsToExport 等字段应显式列出要公开的成员,而非使用通配符 *。这不仅能提升模块加载性能,也便于用户通过 Get-Command -Module 发现可用命令。

  6. 跨平台兼容性声明:如果你的模块仅在 Windows 上可用(例如调用了 Win32 API 或 Windows 专属的 .NET 类),务必在模块清单的 DescriptionPrivateData 中明确说明,并在代码中加入平台检测逻辑,避免 Linux/macOS 用户安装后无法使用而困惑。

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)隔离机制,或在不同项目中使用独立的模块安装路径。