在上一期的专栏中,我向您演示了如何使用 Windows PowerShell 构建相当高级的清单工具。我创建的工具提供了多个有关输出的选项,这应归功于外壳的内置功能和将函数应用于对象。
我所创建的函数有一个无可否认的弱点:它不能适度处理可能发生的任何错误(例如连接或权限问题)。这正是我要在本期的 Windows PowerShell 专栏中加以解决的,我将介绍 Windows PowerShell 所提供的错误处理功能。
设置 Trap
在 Windows PowerShell 中,Trap 关键字定义一个错误处理程序。当您的脚本中出现异常时,外壳会检查是否已经定义 Trap,这意味着它必须在发生任何异常之前出现在脚本中。对于本演示,我将整理出一个会产生连接性问题的测试脚本:我将使用 Get-WmiObject 连接网络中并不存在的计算机名。我的目标是让错误 Trap 将无效计算机名写出到一个文件中,从而为我提供一个记录了无效计算机名的文件。我还将加入到两个有效计算机的连接(我将使用 localhost)。请参见
图 1 中的脚本。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
trap { write-host "Error connecting to $computer" -fore red "$computer" | out-file c:\demo\errors.txt -append continue } $computer = "localhost" get-wmiobject win32_operatingsystem -comp $computer $computer = "server2" get-wmiobject win32_operatingsystem -comp $computer $computer = "localhost" get-wmiobject win32_operatingsystem -comp $computer
此脚本的输出(如
图 2 所示)与我的期望不符。请注意 "Error connecting to…" 消息不显示。也没有创建 Errors.txt 文件。也就是说,根本没有执行我的 Trap。究竟发生了什么?
![](https://technet.microsoft.com/dd297622.fig02_L%28zh-cn%29.gif)
图 2
这不是我所希望的输出!
停止!
关键在于了解正常外壳错误消息与异常不同(分为非终止错误和终止错误。终止错误会停止管道的执行并产生异常)。只有异常才能被捕获。出现错误时,外壳会检查其内置的 $ErrorActionPreference 变量以确定自己要执行的操作。该变量默认含有 "Continue" 值,它表示“显示错误消息并继续”。将此变量更改为 "Stop" 会使其显示错误消息并产生可捕获的异常。但这意味着您脚本中的任何错误也将执行该操作。
更好的方法是只让您认为可能会引发问题的 cmdlet 使用“停止”行为。可以使用 –ErrorAction(或 –EA)参数(一个所有 cmdlet 都支持的常见参数)完成此操作。
图 3 显示了此脚本的修订版本。它将按照预期方式工作,产生的输出如
图 4 所示。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
trap { write-host "Error connecting to $computer" -fore red "$computer" | out-file c:\demo\errors.txt -append continue } $computer = "localhost" get-wmiobject win32_operatingsystem -comp $computer -ea stop $computer = "server2" get-wmiobject win32_operatingsystem -comp $computer -ea stop $computer = "localhost" get-wmiobject win32_operatingsystem -comp $computer -ea stop
![](http://i.technet.microsoft.com/dd297622.fig04_L%28en-us%29.gif)
图 4
使用 –ErrorAction 参数时我获得了更多有用的结果
在 Trap 末尾使用 Continue 指示外壳继续执行产生异常的代码行之后的一行。还可以使用关键字 Break(我将在稍后加以讨论)。另请注意,$computer 变量(在脚本中定义)在 Trap 内仍然有效。这是因为 Trap 是脚本本身的子作用域,即 Trap 可以查看脚本内的所有变量(稍后我也将介绍此方面的更多相关信息)。
在作用域中完成所有操作
Windows PowerShell 中错误捕获的一个尤为棘手的方面是作用域的使用。外壳本身代表全局作用域,它包含外壳内部发生的所有事件。如果您运行某个脚本,它会获取自己的脚本作用域。如果您定义某个函数,该函数的内部便是其自己的专用作用域等等。这将创建一种父/子类型的层次结构。
发生异常时,外壳会在当前作用域内查找 Trap。这意味着某个函数内的异常将在该函数内部查找 Trap。如果外壳发现了 Trap,就会执行该 Trap。如果 Trap 以 Continue 结尾,外壳将继续执行引发异常的代码行后面的一行,但仍在同一作用域中。下面借助一小部分伪代码来说明这一点:
01 Trap { 02 # Log error to a file 03 Continue 04 } 05 Get-WmiObject Win32_Service –comp "Server2" –ea "Stop" 06 Get-Process
如果异常发生在第 5 行,则将执行第 1 行中的 Trap。Trap 以 Continue 结尾,因此将继续执行第 6 行。
现在考虑下面这个略有些不同的作用域示例:
01 Trap { 02 # Log error to a file 03 Continue 04 } 05 06 Function MyFunction { 07 Get-WmiObject Win32_Service –comp "Server2" –ea "Stop" 08 Get-Process 09 } 10 11 MyFunction 12 Write-Host "Testing!"
如果错误发生在第 7 行,则外壳会在函数的作用域内查找 Trap。如果没有找到,那么外壳将退出函数的作用域,继续在父作用域内查找 Trap。因为那里有 Trap,所以它将执行第 1 行。在本例中,代码是 Continue,所以将继续执行同一作用域中异常之后的代码行,即第 12 行,而不是第 8 行。换言之,外壳在退出之后不会再重新进入该函数。
现在将该行为与以下示例做一下对比:
01 Function MyFunction { 02 Trap { 03 # Log error to a file 04 Continue 05 } 06 Get-WmiObject Win32_Service –comp "Server2" –ea "Stop" 07 Get-Process 08 } 09 10 MyFunction 11 Write-Host "Testing!"
在本例中,第 6 行中的错误将执行第 2 行中的 Trap,并保持在函数的作用域内。Continue 关键字将保持在该作用域内,继续执行第 7 行。如果您将 Trap 放入预期会发生错误的作用域内,好处是您仍保持在作用域中并可以在其中继续执行。但如果此方法对于您的情况不适用应该怎么办呢?
断开
我在前面提到过 Break 关键字。
图 5 显示了一个如何运用 Break 关键字的示例。
![](http://i.msdn.microsoft.com/Global/Images/clear.gif)
01 Trap { 02 # Handle the error 03 Continue 04 } 05 06 Function MyFunction { 07 Trap { 08 # Log error to a file 09 If ($condition) { 10 Continue 11 } Else { 12 Break 13 } 14 } 15 Get-WmiObject Win32_Service –comp "Server2" –ea "Stop" 16 Get-Process 17 } 18 19 MyFunction 20 Write-Host "Testing!"
以下简要概述了执行链。首先执行第 19 行,它调用第 6 行中的函数。执行第 15 行并产生异常。该异常在第 7 行捕获,然后 Trap 必须在第 9 行做出决定。假设 $condition 为 True,Trap 将在第 16 行继续执行。
但是,如果 $condition 为 False,Trap 将发生中断。这将退出当前作用域,并将原始异常传递至父项。从外壳角度看,这意味着第 19 行产生了异常,并被第 1 行捕获。Continue 关键字将强制外壳继续执行第 20 行。
实际上,这两个 Trap 中都包含了略多一些的代码,用于处理错误,对其进行记录等等。在本例中我只是省略了这种函数代码,以使实际流程更易于查看。
为什么要担心呢?
您何时需要捕获错误?有两种情况:预测可能会发生错误以及当您想要某种超越普通错误消息的行为时(例如将错误记录到文件或显示更有帮助的错误消息)。
通常我在复杂一些的脚本中加入错误处理,以帮助处理我可以预见发生的错误。这些错误包括但不限于连接不良或权限问题等错误。
错误捕获无疑需要花费更多的时间和精力才能了解。但当您在 Windows PowerShell 中处理更加复杂的任务时,很有必要实施错误捕获,以帮助您构建更加完善、专业的工具。
Don Jones 参与编写了
《Windows PowerShell:TFM》,而且是其他许多 IT 书籍的作者。可通过其博客
concentratedtech.com 与他联系。