适用于 PowerShell 5.1 及以上版本
PowerShell 模块是代码复用的标准单元。将常用的函数封装为模块,不仅可以在不同脚本中轻松调用,还能方便地分享给团队成员或发布到 PowerShell Gallery。一个结构良好的模块包含清单文件(PSD1)、脚本模块(PSM1)、帮助文档和测试用例。掌握模块开发,是从”写脚本”到”做工程”的关键一步。
本文将介绍模块的创建、清单配置、打包发布的完整流程。
创建模块结构
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
| $moduleName = "Utilities" $moduleRoot = "$env:USERPROFILE\Documents\PowerShell\Modules\$moduleName" $version = "1.0.0"
$directories = @( $moduleRoot "$moduleRoot\$version" "$moduleRoot\$version\Public" "$moduleRoot\$version\Private" "$moduleRoot\$version\en-US" )
foreach ($dir in $directories) { if (-not (Test-Path $dir)) { New-Item $dir -ItemType Directory -Force | Out-Null } }
Write-Host "模块目录已创建:$moduleRoot\$version" -ForegroundColor Green
$privateFunction = @' function Get-InternalConfig { [CmdletBinding()] param()
$configPath = Join-Path $env:APPDATA "Utilities\config.json"
if (Test-Path $configPath) { return Get-Content $configPath -Raw | ConvertFrom-Json }
return [PSCustomObject]@{ LogLevel = "Info" OutputPath = "$env:TEMP\Utilities" MaxRecords = 1000 } } '@
$privateFunction | Set-Content "$moduleRoot\$version\Private\Get-InternalConfig.ps1"
$publicFunctions = @( @{ Name = "Get-SystemReport" Content = @' function Get-SystemReport { <# .SYNOPSIS 获取系统信息报告 .DESCRIPTION 收集操作系统、硬件、网络等系统信息并生成报告对象 .OUTPUTS PSCustomObject #> [CmdletBinding()] param( [switch]$IncludeNetwork, [switch]$IncludeServices )
$config = Get-InternalConfig
$report = [PSCustomObject]@{ ComputerName = $env:COMPUTERNAME Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" OS = (Get-CimInstance Win32_OperatingSystem).Caption Version = [System.Environment]::OSVersion.Version.ToString() TotalMemoryGB = [math]::Round( (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2 ) CPU = (Get-CimInstance Win32_Processor).Name }
if ($IncludeNetwork) { $adapters = Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -notmatch '^(127\.|169\.254\.)' } $report | Add-Member -NotePropertyName Network -NotePropertyValue ( $adapters | Select-Object InterfaceAlias, IPAddress, PrefixLength ) }
if ($IncludeServices) { $services = Get-Service | Where-Object { $_.Status -eq 'Running' } $report | Add-Member -NotePropertyName RunningServices -NotePropertyValue $services.Count }
return $report } '@ } @{ Name = "Export-SystemReport" Content = @' function Export-SystemReport { <# .SYNOPSIS 导出系统报告到文件 #> [CmdletBinding()] param( [string]$OutputPath = "$env:TEMP\SystemReport.json", [switch]$IncludeNetwork, [switch]$IncludeServices )
$report = Get-SystemReport -IncludeNetwork:$IncludeNetwork -IncludeServices:$IncludeServices $report | ConvertTo-Json -Depth 5 | Set-Content $OutputPath -Encoding UTF8 Write-Host "报告已导出到:$OutputPath" -ForegroundColor Green return $OutputPath } '@ } )
foreach ($func in $publicFunctions) { $func.Content | Set-Content "$moduleRoot\$version\Public\$($func.Name).ps1" }
Write-Host "已创建 $($publicFunctions.Count) 个公共函数" -ForegroundColor Green
|
执行结果示例:
1 2
| 模块目录已创建:C:\Users\admin\Documents\PowerShell\Modules\Utilities\1.0.0 已创建 2 个公共函数
|
模块清单与入口
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
| $psm1Content = @' # 加载私有函数 $private = Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" -ErrorAction SilentlyContinue foreach ($file in $private) { . $file.FullName }
# 加载公共函数 $public = Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" -ErrorAction SilentlyContinue foreach ($file in $public) { . $file.FullName }
# 导出公共函数 Export-ModuleMember -Function $public.BaseName '@
$psm1Content | Set-Content "$moduleRoot\$version\Utilities.psm1"
$manifestParams = @{ Path = "$moduleRoot\$version\Utilities.psd1" RootModule = "Utilities.psm1" ModuleVersion = $version Author = "Admin" CompanyName = "IT Department" Description = "日常运维工具集" PowerShellVersion = "5.1" FunctionsToExport = @("Get-SystemReport", "Export-SystemReport") CmdletsToExport = @() VariablesToExport = @() AliasesToExport = @() FileList = @("Utilities.psm1", "Utilities.psd1", "Public\Get-SystemReport.ps1", "Public\Export-SystemReport.ps1", "Private\Get-InternalConfig.ps1") PrivateData = @{ PSData = @{ Tags = @("Utilities", "SystemReport", "Admin") LicenseUri = "https://opensource.org/licenses/MIT" ProjectUri = "https://github.com/example/Utilities" } } }
New-ModuleManifest @manifestParams Write-Host "模块清单已创建" -ForegroundColor Green
Test-ModuleManifest "$moduleRoot\$version\Utilities.psd1" | Select-Object Name, Version, Author, Description, ExportedFunctions | Format-List
Import-Module Utilities -Force Get-Command -Module Utilities | Format-Table Name, CommandType -AutoSize
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12
| 模块清单已创建
Name : Utilities Version : 1.0.0 Author : Admin Description : 日常运维工具集 ExportedFunctions : {[Get-SystemReport, Export-SystemReport]}
Name CommandType
Get-SystemReport Function Export-SystemReport Function
|
模块发布与版本管理
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
| function Update-ModuleVersion { param( [Parameter(Mandatory)] [string]$ManifestPath,
[ValidateSet("Major", "Minor", "Patch")] [string]$Bump = "Patch" )
$manifest = Test-ModuleManifest $ManifestPath $current = [version]$manifest.Version
$newVersion = switch ($Bump) { "Major" { [version]::new($current.Major + 1, 0, 0) } "Minor" { [version]::new($current.Major, $current.Minor + 1, 0) } "Patch" { [version]::new($current.Major, $current.Minor, $current.Build + 1) } }
$content = Get-Content $ManifestPath -Raw $content = $content -replace "ModuleVersion = '\d+\.\d+\.\d+'", "ModuleVersion = '$newVersion'" $content | Set-Content $ManifestPath -Encoding UTF8
Write-Host "版本已更新:$current -> $newVersion" -ForegroundColor Green return $newVersion }
Update-ModuleVersion -ManifestPath "$moduleRoot\$version\Utilities.psd1" -Bump Patch
function Publish-ModuleToLocalRepo { param( [string]$ModulePath, [string]$RepositoryPath = "\\fileserver\PSRepository" )
if (-not (Test-Path $RepositoryPath)) { New-Item $RepositoryPath -ItemType Directory -Force | Out-Null }
if (-not (Get-PSRepository -Name "LocalRepo" -ErrorAction SilentlyContinue)) { Register-PSRepository -Name "LocalRepo" ` -SourceLocation $RepositoryPath ` -PublishLocation $RepositoryPath ` -InstallationPolicy Trusted }
Publish-Module -Path $ModulePath -Repository "LocalRepo" Write-Host "模块已发布到本地仓库:$RepositoryPath" -ForegroundColor Green }
$dependencyCheck = { $module = "Utilities" $installed = Get-Module -ListAvailable -Name $module | Sort-Object Version -Descending | Select-Object -First 1
if ($installed) { Write-Host "$module 版本:$($installed.Version)" -ForegroundColor Green
$manifest = Test-ModuleManifest $installed.Path if ($manifest.RequiredModules) { foreach ($req in $manifest.RequiredModules) { $reqInstalled = Get-Module -ListAvailable -Name $req.Name if (-not $reqInstalled) { Write-Host "缺少依赖:$($req.Name)" -ForegroundColor Red } } } } else { Write-Host "未安装模块:$module" -ForegroundColor Yellow } }
& $dependencyCheck
|
执行结果示例:
1 2
| 版本已更新:1.0.0 -> 1.0.1 Utilities 版本:1.0.1
|
注意事项
- 命名规范:模块名使用 PascalCase,函数名使用 Verb-Noun 格式,动词从
Get-Verb 的批准列表中选取
- 清单文件:PSD1 是模块的元数据入口,
FunctionsToExport 应显式列出而非使用通配符,提升加载性能
- 帮助文档:使用基于注释的帮助(
.SYNOPSIS、.DESCRIPTION)或外部 MAML 帮助文件,Get-Help 才能正常显示
- 兼容性:注意区分 PowerShell 5.1 和 7.x 的差异,模块清单的
PowerShellVersion 应如实标注最低要求
- 测试:使用 Pester 框架为公共函数编写单元测试,确保模块升级不破坏现有功能
- 发布:发布到 PowerShell Gallery 前使用
Test-ModuleManifest 和 Test-ScriptFileInfo 验证元数据完整性