浅谈PowerShell 捕获错误

之前的文章我们演示了如何使用 Windows PowerShell 构建相当高级的清单工具。我创建的工具提供了多个有关输出的选项,这应归功于外壳的内置功能和将函数应用于对象。

我所创建的函数有一个无可否认的弱点:它不能适度处理可能发生的任何错误(例如连接或权限问题)。这正是我要在本期的 Windows PowerShell 专栏中加以解决的,我将介绍 Windows PowerShell 所提供的错误处理功能。

设置 Trap

在 Windows PowerShell 中,Trap 关键字定义一个错误处理程序。当您的脚本中出现异常时,外壳会检查是否已经定义 Trap,这意味着它必须在发生任何异常之前出现在脚本中。对于本演示,我将整理出一个会产生连接性问题的测试脚本:我将使用 Get-WmiObject 连接网络中并不存在的计算机名。我的目标是让错误 Trap 将无效计算机名写出到一个文件中,从而为我提供一个记录了无效计算机名的文件。我还将加入到两个有效计算机的连接(我将使用 localhost)。请参见图 1 中的脚本。

添加 Trap

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。究竟发生了什么?

图 2 这不是我所希望的输出!

停止!

关键在于了解正常外壳错误消息与异常不同(分为非终止错误和终止错误。终止错误会停止管道的执行并产生异常)。只有异常才能被捕获。出现错误时,外壳会检查其内置的 $ErrorActionPreference 变量以确定自己要执行的操作。该变量默认含有 "Continue" 值,它表示“显示错误消息并继续”。将此变量更改为 "Stop" 会使其显示错误消息并产生可捕获的异常。但这意味着您脚本中的任何错误也将执行该操作。

更好的方法是只让您认为可能会引发问题的 cmdlet 使用“停止”行为。可以使用 –ErrorAction(或 –EA)参数(一个所有 cmdlet 都支持的常见参数)完成此操作。图 3 显示了此脚本的修订版本。它将按照预期方式工作,产生的输出如图 4 所示。

 使用 -ErrorAction

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

图 4 使用 –ErrorAction 参数时我获得了更多有用的结果

在 Trap 末尾使用 Continue 指示外壳继续执行产生异常的代码行之后的一行。还可以使用关键字 Break(我将在稍后加以讨论)。另请注意,$computer 变量(在脚本中定义)在 Trap 内仍然有效。这是因为 Trap 是脚本本身的子作用域,即 Trap 可以查看脚本内的所有变量(稍后我也将介绍此方面的更多相关信息)。

在作用域中完成所有操作

Windows PowerShell 中错误捕获的一个尤为棘手的方面是作用域的使用。外壳本身代表全局作用域,它包含外壳内部发生的所有事件。如果您运行某个脚本,它会获取自己的脚本作用域。如果您定义某个函数,该函数的内部便是其自己的专用作用域等等。这将创建一种父/子类型的层次结构。

发生异常时,外壳会在当前作用域内查找 Trap。这意味着某个函数内的异常将在该函数内部查找 Trap。如果外壳发现了 Trap,就会执行该 Trap。如果 Trap 以 Continue 结尾,外壳将继续执行引发异常的代码行后面的一行,但仍在同一作用域中。下面借助一小部分伪代码来说明这一点:

Trap {
 # Log error to a file
 Continue
}
Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
Get-Process

如果异常发生在第 5 行,则将执行第 1 行中的 Trap。Trap 以 Continue 结尾,因此将继续执行第 6 行。

现在考虑下面这个略有些不同的作用域示例:

 Trap {
  # Log error to a file
  Continue
 }

 Function MyFunction {
  Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
  Get-Process
 }

 MyFunction
 Write-Host "Testing!"

如果错误发生在第 7 行,则外壳会在函数的作用域内查找 Trap。如果没有找到,那么外壳将退出函数的作用域,继续在父作用域内查找 Trap。因为那里有 Trap,所以它将执行第 1 行。在本例中,代码是 Continue,所以将继续执行同一作用域中异常之后的代码行,即第 12 行,而不是第 8 行。换言之,外壳在退出之后不会再重新进入该函数。

现在将该行为与以下示例做一下对比:

Function MyFunction {
 Trap {
  # Log error to a file
  Continue
 }
 Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
 Get-Process
}

MyFunction
Write-Host "Testing!"

在本例中,第 6 行中的错误将执行第 2 行中的 Trap,并保持在函数的作用域内。Continue 关键字将保持在该作用域内,继续执行第 7 行。如果您将 Trap 放入预期会发生错误的作用域内,好处是您仍保持在作用域中并可以在其中继续执行。但如果此方法对于您的情况不适用应该怎么办呢?

该工具非常适合管理配置基线。Compare-Object(或 Diff)旨在对比两组对象。默认情况下,它将比较每个对象的所有属性,并由该命令输出所有不同之处。所以设想您已将某个服务器的服务完全按照您所需的方式进行了配置。只需运行下面的内容就能创建基线:

Get-Service | Export-CliXML c:\baseline.xml

几乎所有对象都可以输送到 Export-CliXML,它会将对象转换为 XML 文件。而后,您可以运行同一命令(如 Get-Service)并将结果与保存的 XML 进行比较。命令如下:

Compare-Object (Get-Service) (Import-CliXML
 c:\baseline.xml) –property name

添加 –property 参数将强制比较仅查看该属性,而非整个对象。在本例中,您将得到由不同于原始基线的所有服务名称组成的列表,让您了解在创建后基线是否添加或删除了任何服务。

断开

我在前面提到过 Break 关键字。图 5 显示了一个如何运用 Break 关键字的示例。

使用 Break 关键字

 Trap {
  # Handle the error
  Continue
 }

 Function MyFunction {
  Trap {
   # Log error to a file
   If ($condition) {
    Continue
   } Else {
    Break
   }
  }
  Get-WmiObject Win32_Service –comp "Server2" –ea "Stop"
  Get-Process
 }

 MyFunction
 Write-Host "Testing!"

以下简要概述了执行链。首先执行第 19 行,它调用第 6 行中的函数。执行第 15 行并产生异常。该异常在第 7 行捕获,然后 Trap 必须在第 9 行做出决定。假设 $condition 为 True,Trap 将在第 16 行继续执行。

但是,如果 $condition 为 False,Trap 将发生中断。这将退出当前作用域,并将原始异常传递至父项。从外壳角度看,这意味着第 19 行产生了异常,并被第 1 行捕获。Continue 关键字将强制外壳继续执行第 20 行。

实际上,这两个 Trap 中都包含了略多一些的代码,用于处理错误,对其进行记录等等。在本例中我只是省略了这种函数代码,以使实际流程更易于查看。

为什么要担心呢?

您何时需要捕获错误?有两种情况:预测可能会发生错误以及当您想要某种超越普通错误消息的行为时(例如将错误记录到文件或显示更有帮助的错误消息)。

通常我在复杂一些的脚本中加入错误处理,以帮助处理我可以预见发生的错误。这些错误包括但不限于连接不良或权限问题等错误。

错误捕获无疑需要花费更多的时间和精力才能了解。但当您在 Windows PowerShell 中处理更加复杂的任务时,很有必要实施错误捕获,以帮助您构建更加完善、专业的工具。

(0)

相关推荐

  • Powershell小技巧之找出脚本中的错误

    找出脚本之中的语法错误从来就不是轻松的事情,但是可以这样去筛选: filter Test-SyntaxError { $text = Get-Content -Path $_.FullName if ($text.Length -gt 0) { $err = $null $null = [System.Management.Automation.PSParser]::Tokenize($text, [ref] $err) if ($err) { $_ } } } 这个脚本中,你可以快速扫描一个目

  • PowerShell中查询错误编号信息的2个方法

    有时候,一些本地命令,比如net.exe,在执行过程中,会返回数字编号形式的错误信息.很郁闷的是,我们不知道这些数字代表什么意思.以前,我们可以使用如下的方式去获取数字代表的信息: 复制代码 代码如下: PS> net helpmsg 3534 The service did not report an error. PS> net helpmsg 1 Incorrect function. PS> net helpmsg 4323 The transport cannot access

  • PowerShell捕获错误的2种方法(异常捕获命令、错误变量)

    在先前的技巧中你能观察到使用了 "-ErrorAction Stop"结合"异常捕获命令"能捕获一个Powershell命令的错误,可是使用了这种方式操作之后,脚本会在第一个错误发生后停止. 下面举例:使用Powershell递归扫描文件夹.它将不能完成捕获中间所有的异常(例如某些子文件夹是受访问保护的). 复制代码 代码如下: try{  Get-ChildItem -Path $env:windir -Filter *.ps1 -Recurse -ErrorAc

  • Powershell错误处理之what-if

    自动化具有高度方便的特点,同时也可能会自动产生一些不可避免的错误.这也就是Powershell为什么会有一些专门来防止和处理危险的机制:这些机制会对接下来要执行的操作进行确认. 试运行:模拟操作 如果你想知道一个确定的命令会产生什么影响,你可以进行试运行.这时,Powershell不会执行任何对系统有影响的操作,只会告诉你如果没有模拟运行,可能产生什么影响和后果.通过-whatif 参数.事实上,许多cmdltes都支持试运行. #如果执行stop-process -name *a*会终止下面的

  • Powershell小技巧之用变量累积记录错误

    当你使用GC查询文件时,当你没有足够的权限你可能会错过不少错误.你可以使用"–ErrorAction SilentlyContinue"忽略这些错误. 有一个不错的方法来获取这些错误. 我们在Windows目录中查询所有PS脚本文件,将它的文件保存在$PSScripts,同时将错误日志记录在变量ErrorList中: 复制代码 代码如下: $PSScripts = Get-ChildItem -Path c:\windows -Filter *.ps1 -Recurse -ErrorA

  • 浅谈PowerShell 捕获错误

    之前的文章我们演示了如何使用 Windows PowerShell 构建相当高级的清单工具.我创建的工具提供了多个有关输出的选项,这应归功于外壳的内置功能和将函数应用于对象. 我所创建的函数有一个无可否认的弱点:它不能适度处理可能发生的任何错误(例如连接或权限问题).这正是我要在本期的 Windows PowerShell 专栏中加以解决的,我将介绍 Windows PowerShell 所提供的错误处理功能. 设置 Trap 在 Windows PowerShell 中,Trap 关键字定义一

  • 浅谈php自定义错误日志

    平时经常看php的错误日志,很少有机会去自己动手写日志,看了王健的<最佳日志实践>觉得写一个清晰明了,结构分明的日志还是非常有必要的. 在写日志前,我们问问自己:为什么我们有时要记录自定义的日志呢?而不用系统默认的日志记录方式呢? 我认为有两个原因: 1.团队需要一个统一格式的日志方便管理 2.大量无用错误日志占据硬盘空间,仅需记录有意义的日志. 那么,实践一下. 1.打开你的php.ini 2.打开日志记录,将 复制代码 代码如下: log_errors = Off 改成 复制代码 代码如下

  • 浅谈PHP错误类型及屏蔽方法

    程序只要在运行,就免不了会出现错误,错误很常见,比如Error,Notice,Warning等等.在PHP中,主要有以下3种错误类型. 1.注意(Notices) 这些都是比较小而且不严重的错误,比如去访问一个未被定义的变量.通常,这类的错误是不提示给用户的,但有时这些错误会影响到运行的结果. 2.警告(Warnings) 这就是稍微严重一些的错误了,比如想要包含include()一个本身不存在的文件.这样的错误信息会提示给用户,但不会导致程序终止运行. 3.致命错误(Fatal errors)

  • 浅谈python中的错误与异常

    目录 一.语法错误 二.异常处理 2.1.try-finally语句 2.2.raise 语句 2.3.assert 断言语句 三.小结 一.语法错误 异常:大多数的异常都不会被程序处理,都以错误信息的形式展现在这里 二.异常处理 while True: try: x = int(input("请输入一个错误:")) break except ValueError: print("不是有效数字,再试一遍") try 语句执行顺序: 先执行try语句里面的语句,如果没

  • 浅谈PHP正则中的捕获组与非捕获组

    今天遇到一个正则匹配的问题,忽然翻到有捕获组的概念,手册上也是一略而过,百度时无意翻到C#和Java中有对正则捕获组的特殊用法,搜索关键词有PHP时竟然没有相关内容,自己试了一下,发现在PHP中也是可行的,于是总结一下,分享的同时也希望有大神和细心的学习者找到我理解中出现的问题. 什么是捕获组 我们先看一下PHP的正则匹配函数 int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags =

  • 浅谈JavaScript正则表达式-非捕获性分组

    非捕获性分组定义子表达式可以作为整体被修饰但是子表达式匹配结果不会被存储. 非捕获性分组通过将子表达式放在"?:"符号后. str = "img1.jpg,img2.jpg,img3.bmp"; reg = /(?:\w*)(?=\.gif)/; arr_m = str.match(reg);//arr_m = ["img1","img2"] 你在期待什么还是在等待什么?你选择了什么还是只想浮徒一生?茫茫人海,真的需要那么回眸

  • 浅谈使用Python变量时要避免的3个错误

    Python编程中经常遇到一些莫名其妙的错误, 其实这不是语言本身的问题, 而是我们忽略了语言本身的一些特性导致的,今天就来看下使用Python变量时导致的3个不可思议的错误, 以后在编程中要多多注意. 关于Python编程运行时新手易犯错误,这里暂不作介绍,详情参见:Python运行的17个时新手常见错误小结 1. 可变数据类型作为函数定义中的默认参数 这似乎是对的?你写了一个小函数,比如,搜索当前页面上的链接,并可选将其附加到另一个提供的列表中. def search_for_links(p

  • 浅谈C++的浅拷贝出现的错误

    之前看一些资料提到浅拷贝的问题,即在复制对象时,只是对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝.如果对象中存在动态成员,如指针,那么仅仅做浅拷贝是不够的,并且容易引发错误,最经典的例子: #include <iostream> #include <stdio.h> using namespace std; class A{ public: A(){m_p = new int(10);}; ~A(){cout << "destructio

  • 浅谈PHP中的错误处理和异常处理

    错误处理:          1. 语法错误     2. 运行时的错误     3. 逻辑错误 错误报告:                  错误E_ERROR         警告E_WARNING         注意E_NOTICE 开发阶段:开发时输出所有的错误报告,有利于我们进行调试 运行阶段:不要让程序输出任何一种错误报告 将错误报告写入日志中 一.   指定错误报告error_reporting=E_ALL(在php.inn) 二.   关闭错误输出display_errors=

随机推荐