Windows PowerShell 捕獲錯誤

Don Jones

目錄

設置 Trap
停止!
在作用域中完成所有操作
斷開
為什麼要擔心呢?

在上一期的專欄中,我向您演示了如何使用 Windows PowerShell 構建相當高級的清單工具。 我創建的工具提供了多個有關輸出的選項,這應歸功於外殼的內置功能和將函數應用於物件。

我所創建的函數有一個無可否認的弱點:它不能適度處理可能發生的任何錯誤(例如連接或許可權問題)。 這正是我要在本期的 Windows PowerShell 專欄中加以解決的,我將介紹 Windows PowerShell 所提供的錯誤處理功能。

設置 Trap

在 Windows PowerShell 中,Trap 關鍵字定義一個錯誤處理常式。 當您的腳本中出現異常時,外殼會檢查是否已經定義 Trap,這意味著它必須在發生任何異常之前出現在腳本中。 對於本演示,我將整理出一個會產生連線性問題的測試腳本:我將使用 Get-WmiObject 連接網路中並不存在的電腦名。 我的目標是讓錯誤 Trap 將無效電腦名寫出到一個檔中,從而為我提供一個記錄了無效電腦名的檔。 我還將加入到兩個有效電腦的連接(我將使用 localhost)。 請參見圖 1 中的腳本。

圖 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。 究竟發生了什麼?

fig02.gif

圖 2 這不是我所希望的輸出!

停止!

關鍵在於瞭解正常外殼錯誤消息與異常不同(分為非終止錯誤和終止錯誤。 終止錯誤會停止管道的執行並產生異常)。 只有異常才能被捕獲。 出現錯誤時,外殼會檢查其內置的 $ErrorActionPreference 變數以確定自己要執行的操作。 該變數預設含有 "Continue" 值,它表示“顯示錯誤消息並繼續”。 將此變數更改為 "Stop" 會使其顯示錯誤消息並產生可捕獲的異常。 但這意味著您腳本中的任何錯誤也將執行該操作。

更好的方法是只讓您認為可能會引發問題的 cmdlet 使用“停止”行為。 可以使用 –ErrorAction(或 –EA)參數(一個所有 cmdlet 都支援的常見參數)完成此操作。 圖 3 顯示了此腳本的修訂版本。 它將按照預期方式工作,產生的輸出如圖 4 所示。

圖 3 使用 -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 結尾,外殼將繼續執行引發異常的代碼行後面的一行,但仍在同一作用域中。 下麵借助一小部分偽代碼來說明這一點:

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 放入預期會發生錯誤的作用域內,好處是您仍保持在作用域中並可以在其中繼續執行。 但如果此方法對於您的情況不適用應該怎麼辦呢?

本月 Cmdlet:Compare-Object

該工具非常適合管理配置基線。 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 關鍵字的示例。

圖 5 使用 Break 關鍵字

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與他聯繫。