Windows Server 2025 / 处理 PowerShell 错误并记录执行日志

将 try、catch、finally 与明确的错误处理方式结合,并为每次 PowerShell 执行单独保存日志,可以追踪失败原因和部分完成的结果。命令已经结束,并不能单独证明整项管理任务已经成功。

让失败可被识别

本练习读取文件并写入日志,不修改服务器角色、服务或网络配置。实际的安装或账户管理脚本还需要记录:某一步失败之前,哪些修改已经生效。进入 catch 不会自动撤销之前的操作。

有些错误允许 PowerShell 继续执行,默认不会触发 catch。因此,仅把命令放进 try 并不足够;-ErrorAction Stop 可以将支持的非终止性错误提升为可捕获的终止性错误。Microsoft 的 PowerShell 错误处理说明介绍了这些区别。

准备输入文件与完整脚本

先创建 C:\LabScripts,再将 C:\LabScripts\input.txt 保存为只包含一行原样文本 lab ready 的纯文本文件。将下面的完整代码保存为 C:\LabScripts\Read-LabFile.ps1,并检查 .txt 和 .ps1 扩展名。使用 UTF-8;如果在脚本中加入中文等非 ASCII 字符,应选择带字节顺序标记(BOM,byte order mark)的 UTF-8,参阅字符编码说明。使用 Windows PowerShell 5.1 运行,并确保有权读取输入文件以及创建或写入 C:\LabLogs。脚本以 JSON(JavaScript Object Notation,JavaScript 对象表示法)保存结构化结果,文件名包含时间戳和短标识符。

param(
    [Parameter(Mandatory=$true)]
    [string]$InputPath
)
$logDirectory = 'C:\LabLogs'
New-Item -ItemType Directory -Path $logDirectory -Force -ErrorAction Stop | Out-Null
$runLabel = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + ([guid]::NewGuid().ToString('N').Substring(0,6))
$logPath = Join-Path $logDirectory ($runLabel + '.json')
$result = [ordered]@{
    StartedAt = (Get-Date).ToString('o')
    ComputerName = $env:COMPUTERNAME
    InputPath = $InputPath
    Success = $false
    LineCount = $null
    ErrorId = $null
    ErrorMessage = $null
}
$exitStatus = 0
try {
    $lines = @(Get-Content -LiteralPath $InputPath -ErrorAction Stop)
    $result.LineCount = $lines.Count
    $result.Success = $true
}
catch {
    $result.ErrorId = $_.FullyQualifiedErrorId
    $result.ErrorMessage = $_.Exception.Message
    $exitStatus = 1
    Write-Warning $result.ErrorMessage
}
finally {
    $result.FinishedAt = (Get-Date).ToString('o')
    $result | ConvertTo-Json | Set-Content -LiteralPath $logPath -Encoding UTF8 -ErrorAction Stop
}
Write-Output $logPath
exit $exitStatus

在相同条件下比较成功与失败

请在 PowerShell 控制台中执行这些命令。每次调用 powershell.exe 都会启动子进程,exit 结束的是该子进程,原控制台仍可继续使用。先调用现有文件,立即查看紧接着的 $LASTEXITCODE;再对 missing-input.txt 重复这一对命令,并确认该文件确实不存在。无需删除有效输入或修改系统文件来制造失败:

powershell.exe -NoProfile -File 'C:\LabScripts\Read-LabFile.ps1' -InputPath 'C:\LabScripts\input.txt'
$LASTEXITCODE
powershell.exe -NoProfile -File 'C:\LabScripts\Read-LabFile.ps1' -InputPath 'C:\LabScripts\missing-input.txt'
$LASTEXITCODE

对于一行有效输入,第一次执行应返回退出代码 0,并生成 Success=true、LineCount=1 且无错误详情的 JSON。文件不存在时应返回 1,JSON 中 Success=false、LineCount=null,同时 ErrorId 和 ErrorMessage 有值;错误消息可能随系统语言变化。用文本编辑器打开每次执行实际输出路径对应的 JSON,核对 InputPath、ComputerName、StartedAt 和 FinishedAt。比较这两份新日志,不要误用文件夹里的旧记录。

如果执行策略阻止脚本运行,请遵守组织的执行与签名要求,不要为了本例降低全局设置。

理解各个错误处理区块

区块 本脚本中的作用
try 读取文件并记录行数
catch 记录捕获到的错误标识与消息,设置失败退出代码
finally 无论读取成功还是失败,都尝试保存本次结果

Microsoft 的 try、catch、finally 参考文档说明了终止性错误的处理。本脚本在 try 之前创建 C:\LabLogs:如果准备阶段失败,后面的 catch 不会捕获这个错误。finally 会尝试保存结果,但不能保证有可用空间或写入权限。Success 表示读取是否成功,还需要验证新的 JSON 文件是否有效生成。日志写入也使用 -ErrorAction Stop;没有日志并不能证明任务已无误完成。

外部程序有自己的退出代码。在 Windows PowerShell 5.1 中,非零退出代码不会自动触发 catch。应在程序结束后立即检查 $LASTEXITCODE,必要时用 throw 产生终止性错误。后续外部程序可能覆盖该值;单独使用 -ErrorAction Stop 不会把外部退出代码转换为异常。

为运维脚本定义安全重试条件

  • 记录服务器、目标、开始时间和结束时间。
  • 先查询现有状态,避免重复创建或意外覆盖。
  • 分别记录已成功和已失败的步骤。
  • 不要将密码、令牌或不必要的用户文件内容写入日志。
  • 如果实现回滚,即撤销已执行的修改,应明确支持的范围及恢复失败的情况。

重新使用 input.txt 的有效路径再运行一次,确认新的退出代码为 0,新的 JSON 中 Success=true、LineCount=1。先保留必要的检查记录,再仅清理已经准确识别的本练习临时文件。给所有命令添加 SilentlyContinue 只会隐藏错误,不能替代错误处理。