适用于 PowerShell 5.1 及以上版本
几乎所有脚本都涉及配置管理——数据库连接字符串、API 端点、超时设置、日志级别。如何存储、加载、验证、合并配置,直接影响脚本的可维护性和灵活性。硬编码的配置难以适应不同环境,缺少验证的配置导致运行时错误,没有合并机制的配置无法覆盖默认值。
本文将讲解 PowerShell 中的配置管理模式,从简单的配置文件到多层配置合并。
配置文件格式
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
| $jsonConfig = @" { "appName": "MyApp", "version": "2.5.0", "database": { "server": "prod-db01", "port": 5432, "name": "myapp_production", "timeout": 30 }, "logging": { "level": "Warning", "path": "C:\\Logs\\MyApp", "maxSizeMB": 100 }, "features": { "enableCache": true, "enableMetrics": true, "maxRetries": 3 } } "@
$jsonConfig | Set-Content "C:\MyApp\config.json" -Encoding UTF8
$config = Get-Content "C:\MyApp\config.json" -Raw | ConvertFrom-Json Write-Host "应用名:$($config.appName)" Write-Host "数据库:$($config.database.server):$($config.database.port)" Write-Host "日志级别:$($config.logging.level)"
$psd1Config = @" @{ AppName = 'MyApp' Version = '2.5.0' Database = @{ Server = 'prod-db01' Port = 5432 Name = 'myapp_production' Timeout = 30 } Logging = @{ Level = 'Warning' Path = 'C:\Logs\MyApp' MaxSizeMB = 100 } } "@
$psd1Config | Set-Content "C:\MyApp\config.psd1" -Encoding UTF8
$psd1Data = Invoke-Expression (Get-Content "C:\MyApp\config.psd1" -Raw) Write-Host "`nPSD1 数据库配置:$($psd1Data.Database.Server)"
$env:MYAPP_DB_SERVER = "prod-db01" $env:MYAPP_DB_PORT = "5432" $env:MYAPP_LOG_LEVEL = "Debug" Write-Host "环境变量配置:$env:MYAPP_DB_SERVER`:$env:MYAPP_DB_PORT"
|
执行结果示例:
1 2 3 4 5 6
| 应用名:MyApp 数据库:prod-db01:5432 日志级别:Warning
PSD1 数据库配置:prod-db01 环境变量配置:prod-db01:5432
|
多层配置合并
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
| function Get-ApplicationConfig { param( [string]$ConfigPath = "C:\MyApp\config.json", [string]$Environment = "production" )
$defaults = @{ appName = "MyApp" version = "1.0.0" database = @{ server = "localhost" port = 5432 name = "myapp" timeout = 30 } logging = @{ level = "Info" path = "C:\Logs\MyApp" maxSizeMB = 50 } features = @{ enableCache = $true enableMetrics = $false maxRetries = 3 } }
if (Test-Path $ConfigPath) { $fileConfig = Get-Content $ConfigPath -Raw | ConvertFrom-Json $defaults = Merge-Config $defaults (ConvertTo-Hashtable $fileConfig) Write-Host "已加载配置文件:$ConfigPath" -ForegroundColor Green }
$envConfigPath = $ConfigPath -replace '\.json$', ".$Environment.json" if (Test-Path $envConfigPath) { $envConfig = Get-Content $envConfigPath -Raw | ConvertFrom-Json $defaults = Merge-Config $defaults (ConvertTo-Hashtable $envConfig) Write-Host "已加载环境配置:$envConfigPath" -ForegroundColor Green }
$envPrefix = "MYAPP_" Get-ChildItem Env: | Where-Object { $_.Name -like "$envPrefix*" } | ForEach-Object { $key = $_.Name.Substring($envPrefix.Length).Replace("_", ".") Set-NestedValue -Config $defaults -Path $key -Value $_.Value }
return $defaults }
function Merge-Config { param($Base, $Override)
$result = @{} foreach ($key in $Base.Keys) { $result[$key] = $Base[$key] }
foreach ($key in $Override.Keys) { if ($result[$key] -is [hashtable] -and $Override[$key] -is [hashtable]) { $result[$key] = Merge-Config $result[$key] $Override[$key] } else { $result[$key] = $Override[$key] } } return $result }
function ConvertTo-Hashtable { param([Parameter(Mandatory)][object]$InputObject) if ($InputObject -is [System.Management.Automation.PSCustomObject]) { $hash = @{} $InputObject.PSObject.Properties | ForEach-Object { $hash[$_.Name] = ConvertTo-Hashtable $_.Value } return $hash } return $InputObject }
function Set-NestedValue { param($Config, [string]$Path, $Value)
$keys = $Path -split '\.' $current = $Config for ($i = 0; $i -lt $keys.Count - 1; $i++) { if (-not $current[$keys[$i]]) { $current[$keys[$i]] = @{} } $current = $current[$keys[$i]] } $lastKey = $keys[-1] if ($Value -match '^\d+$') { $Value = [int]$Value } elseif ($Value -match '^(true|false)$') { $Value = $Value -eq 'true' } $current[$lastKey] = $Value }
$config = Get-ApplicationConfig -ConfigPath "C:\MyApp\config.json" -Environment "production" Write-Host "`n最终配置:" -ForegroundColor Cyan Write-Host " 应用:$($config.appName) v$($config.version)" Write-Host " 数据库:$($config.database.server):$($config.database.port)/$($config.database.name)" Write-Host " 日志级别:$($config.logging.level)"
|
执行结果示例:
1 2 3 4 5
| 已加载配置文件:C:\MyApp\config.json 最终配置: 应用:MyApp v2.5.0 数据库:prod-db01:5432/myapp_production 日志级别:Warning
|
配置验证
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
| function Test-ConfigSchema { param( [Parameter(Mandatory)] [hashtable]$Config,
[hashtable]$Schema )
$errors = @()
foreach ($key in $Schema.Keys) { $rule = $Schema[$key]
if ($rule.Required -and -not $Config.ContainsKey($key)) { $errors += "缺少必填配置:$key" continue }
if (-not $Config.ContainsKey($key)) { continue }
$value = $Config[$key]
if ($rule.Type -and $value -isnot $rule.Type) { $errors += "$key 类型错误:期望 $($rule.Type.Name),实际 $($value.GetType().Name)" }
if ($rule.Min -and $value -lt $rule.Min) { $errors += "$key 值 $value 小于最小值 $($rule.Min)" } if ($rule.Max -and $value -gt $rule.Max) { $errors += "$key 值 $value 大于最大值 $($rule.Max)" }
if ($rule.ValidValues -and $value -notin $rule.ValidValues) { $errors += "$key 值 '$value' 不在允许列表中:$($rule.ValidValues -join ', ')" }
if ($rule.Pattern -and $value -notmatch $rule.Pattern) { $errors += "$key 值 '$value' 不匹配模式:$($rule.Pattern)" } }
if ($errors) { Write-Host "配置验证失败:" -ForegroundColor Red $errors | ForEach-Object { Write-Host " ! $_" -ForegroundColor Red } return $false }
Write-Host "配置验证通过" -ForegroundColor Green return $true }
$schema = @{ appName = @{ Required = $true; Type = [string] } version = @{ Required = $true; Pattern = '^\d+\.\d+\.\d+$' } database = @{ Required = $true } logging = @{ Required = $false } }
$validConfig = @{ appName = "MyApp" version = "2.5.0" database = @{ server = "localhost" } }
Test-ConfigSchema -Config $validConfig -Schema $schema
$invalidConfig = @{ appName = 123 version = "invalid" }
Test-ConfigSchema -Config $invalidConfig -Schema $schema
|
执行结果示例:
1 2 3 4 5
| 配置验证通过 配置验证失败: ! appName 类型错误:期望 String,实际 Int32 ! version 值 'invalid' 不匹配模式:^\d+\.\d+\.\d+$ ! 缺少必填配置:database
|
注意事项
- 敏感配置:密码、Token 等敏感配置不应存储在配置文件中,使用环境变量或密钥管理服务
- 配置优先级:明确文档化配置层的优先级,让用户知道哪个配置源优先生效
- 默认值:所有配置都应有合理的默认值,确保脚本在缺少配置文件时仍能运行
- 配置热加载:长时间运行的服务应支持配置热加载,无需重启
- PSD1 安全:
Invoke-Expression 加载 PSD1 存在安全风险,生产环境使用 Import-PowerShellDataFile
- 环境隔离:不同环境(开发、测试、生产)的配置应完全独立,避免误操作