跳到主要内容

错误处理

健壮的脚本需要完善的错误处理机制。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 有多个输出流:

编号说明
Success1正常输出
Error2错误输出
Warning3警告输出
Verbose4详细输出
Debug5调试输出
Information6信息输出

重定向错误流

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 代码。