错误处理
健壮的脚本需要完善的错误处理机制。PowerShell 提供了多种错误处理方式,包括 try-catch 结构、错误流控制、错误变量等,帮助开发者编写可靠的脚本。
错误类型
PowerShell 的错误分为两类:
终止错误(Terminating Errors):完全停止脚本执行,如语法错误、调用不存在的方法。这类错误需要使用 try-catch 捕获。
非终止错误(Non-Terminating Errors):不会停止脚本执行,如文件不存在、权限不足。这类错误默认只显示错误信息,脚本继续执行。
try-catch-finally
基本结构
try {
$result = 1 / 0
}
catch {
Write-Host "发生错误: $_"
}
finally {
Write-Host "清理工作"
}
try:包含可能出错的代码catch:捕获并处理错误finally:无论是否出错都执行,用于清理资源
捕获特定异常
try {
$content = Get-Content -Path "C:\NonExistent\file.txt"
}
catch [System.IO.FileNotFoundException] {
Write-Host "文件未找到: $_"
}
catch [System.UnauthorizedAccessException] {
Write-Host "权限不足: $_"
}
catch {
Write-Host "其他错误: $_"
}
访问错误信息
try {
$result = 1 / 0
}
catch {
Write-Host "错误消息: $($_.Exception.Message)"
Write-Host "错误类型: $($_.Exception.GetType().FullName)"
Write-Host "堆栈跟踪: $($_.ScriptStackTrace)"
}
多个 catch 块
try {
$process = Get-Process -Name "NonExistentProcess" -ErrorAction Stop
}
catch [Microsoft.PowerShell.Commands.ProcessCommandException] {
Write-Host "进程不存在"
}
catch {
Write-Host "其他错误: $_"
}
ErrorAction 参数
ErrorActionPreference
$ErrorActionPreference 变量控制默认错误处理行为:
$ErrorActionPreference = "Stop"
$ErrorActionPreference = "Continue"
$ErrorActionPreference = "SilentlyContinue"
$ErrorActionPreference = "Ignore"
-ErrorAction 参数
为单个命令设置错误处理方式:
Get-Process -Name "NonExistent" -ErrorAction SilentlyContinue
Get-Content -Path "C:\NonExistent.txt" -ErrorAction Stop
常用值:
| 值 | 说明 |
|---|---|
Continue | 显示错误,继续执行(默认) |
SilentlyContinue | 不显示错误,继续执行 |
Stop | 显示错误,停止执行(触发 catch) |
Ignore | 不显示错误,不记录到 $Error |
Inquire | 询问用户如何处理 |
将非终止错误转为终止错误
使用 -ErrorAction Stop 可以将非终止错误转换为终止错误,从而被 try-catch 捕获:
try {
Get-Content -Path "C:\NonExistent.txt" -ErrorAction Stop
}
catch {
Write-Host "无法读取文件: $_"
}
错误变量
$Error 变量
$Error 是一个数组,存储所有发生的错误:
$Error.Count
$Error[0]
$Error[0].Exception.Message
清除错误记录:
$Error.Clear()
-ErrorVariable 参数
将错误存储到指定变量:
Get-Process -Name "NonExistent" -ErrorVariable myError -ErrorAction SilentlyContinue
if ($myError) {
Write-Host "发生错误: $myError"
}
追加错误到变量:
Get-Process -Name "NonExistent1" -ErrorVariable errors -ErrorAction SilentlyContinue
Get-Process -Name "NonExistent2" -ErrorVariable +errors -ErrorAction SilentlyContinue
$errors | ForEach-Object { Write-Host $_.Exception.Message }
throw 语句
抛出错误
使用 throw 手动抛出错误:
function Divide {
param($a, $b)
if ($b -eq 0) {
throw "除数不能为零"
}
return $a / $b
}
抛出特定异常:
throw [System.DivideByZeroException]::new("除数不能为零")
throw [System.IO.FileNotFoundException]::new("文件不存在: $path")
自定义错误
class ValidationException : System.Exception {
ValidationException([string]$message) : base($message) {}
}
function Set-Config {
param([string]$Value)
if ($Value.Length -lt 5) {
throw [ValidationException]::new("配置值长度必须大于5")
}
}
trap 语句
trap 是传统的错误处理方式,在作用域内捕获错误:
trap {
Write-Host "捕获到错误: $_"
continue
}
Get-Content -Path "C:\NonExistent.txt"
Write-Host "脚本继续执行"
trap 后跟 continue 继续执行,跟 break 停止执行:
trap {
Write-Host "错误: $_"
break
}
捕获特定异常:
trap [System.IO.FileNotFoundException] {
Write-Host "文件未找到"
continue
}
trap {
Write-Host "其他错误: $_"
continue
}
错误流
PowerShell 有多个输出流:
| 流 | 编号 | 说明 |
|---|---|---|
| Success | 1 | 正常输出 |
| Error | 2 | 错误输出 |
| Warning | 3 | 警告输出 |
| Verbose | 4 | 详细输出 |
| Debug | 5 | 调试输出 |
| Information | 6 | 信息输出 |
重定向错误流
Get-Process -Name "NonExistent" 2> errors.txt
Get-Process -Name "NonExistent" 2>&1
Get-Process -Name "NonExistent" 2>$null
Write-Error
输出非终止错误:
Write-Error "这是一个错误消息"
Write-Error -Message "错误详情" -Category InvalidArgument
Write-Warning
输出警告:
Write-Warning "这是一个警告"
Write-Verbose
输出详细信息:
Write-Verbose "详细操作信息"
需要使用 -Verbose 参数才会显示:
function Test-Function {
[CmdletBinding()]
param()
Write-Verbose "正在执行..."
}
Test-Function -Verbose
错误处理最佳实践
1. 使用 try-catch 处理关键操作
function Copy-Backup {
param([string]$Source, [string]$Destination)
try {
Copy-Item -Path $Source -Destination $Destination -ErrorAction Stop
Write-Host "备份成功"
}
catch [System.IO.FileNotFoundException] {
Write-Error "源文件不存在: $Source"
}
catch [System.UnauthorizedAccessException] {
Write-Error "权限不足,无法访问: $Destination"
}
catch {
Write-Error "备份失败: $_"
}
}
2. 记录错误日志
function Write-ErrorLog {
param(
[string]$Message,
[string]$LogFile = "C:\Logs\errors.log"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$entry = "[$timestamp] $Message"
$logDir = Split-Path $LogFile -Parent
if (!(Test-Path $logDir)) {
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
}
Add-Content -Path $LogFile -Value $entry
}
try {
}
catch {
Write-ErrorLog -Message $_.Exception.Message
throw
}
3. 清理资源
$stream = $null
try {
$stream = [System.IO.StreamReader]::new("C:\Temp\file.txt")
$content = $stream.ReadToEnd()
}
catch {
Write-Host "读取失败: $_"
}
finally {
if ($stream) {
$stream.Close()
$stream.Dispose()
}
}
4. 使用 -ErrorVariable 检查结果
$result = Get-Process -Name "chrome" -ErrorVariable err -ErrorAction SilentlyContinue
if ($err) {
Write-Host "Chrome 未运行"
} else {
Write-Host "Chrome 正在运行,PID: $($result.Id)"
}
5. 验证输入
function Set-Configuration {
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$ConfigPath,
[Parameter(Mandatory=$true)]
[ValidateSet("Development", "Staging", "Production")]
[string]$Environment
)
if (!(Test-Path $ConfigPath)) {
throw "配置文件不存在: $ConfigPath"
}
}
实用示例
带重试的操作
function Invoke-WithRetry {
param(
[scriptblock]$ScriptBlock,
[int]$MaxRetries = 3,
[int]$DelaySeconds = 2
)
$attempt = 0
while ($attempt -lt $MaxRetries) {
$attempt++
try {
$result = & $ScriptBlock
return $result
}
catch {
if ($attempt -eq $MaxRetries) {
throw
}
Write-Warning "第 $attempt 次尝试失败,$DelaySeconds 秒后重试..."
Start-Sleep -Seconds $DelaySeconds
}
}
}
$result = Invoke-WithRetry -ScriptBlock {
Invoke-RestMethod -Uri "https://api.example.com/data"
} -MaxRetries 5 -DelaySeconds 3
错误处理包装器
function Invoke-SafeOperation {
param(
[scriptblock]$Operation,
[string]$OperationName = "操作",
[switch]$ContinueOnError
)
try {
$result = & $Operation
return @{
Success = $true
Result = $result
Error = $null
}
}
catch {
$errorInfo = @{
Success = $false
Result = $null
Error = $_.Exception.Message
StackTrace = $_.ScriptStackTrace
}
Write-Error "$OperationName 失败: $($_.Exception.Message)"
if ($ContinueOnError) {
return $errorInfo
} else {
throw
}
}
}
$result = Invoke-SafeOperation -Operation {
Get-Content -Path "C:\Config\app.json" | ConvertFrom-Json
} -OperationName "加载配置" -ContinueOnError
下一步
掌握了错误处理后,接下来学习 模块,了解如何组织和分发 PowerShell 代码。