Windows PowerShellエラーをトラップする

Don Jones

目次

トラップを設定する
スクリプトを停止する
すべてはスコープの中に
スコープを抜け出す
心配は無用

前回のコラムでは、Windows PowerShell を使用して、より高度なインベントリ ツールを作成する方法を紹介しました。このツールでは、出力に関する複数のオプションを提供することができました。これはシェルの組み込み機能と、関数でオブジェクトを使用したことのおかげです。

私が作成した関数の明らかな短所は、接続やアクセス許可などの問題が原因で発生するエラーに適切に対処できないことです。今月の Windows PowerShell コラムでは、この問題に対処するために、Windows PowerShell で提供されるエラー処理機能について説明します。

トラップを設定する

Windows PowerShell の Trap キーワードを使用すると、エラー ハンドラを定義できます。スクリプト内で例外が発生すると、トラップが定義されているかどうかがシェルによって確認されます。つまり、スクリプト内で、例外が発生する箇所よりも前にトラップを記述する必要があります。今回のデモンストレーションでは、接続の問題を意図的に発生させるテスト スクリプトを作成します。このスクリプトでは、Get-WmiObject を使用して、あるコンピュータに接続しますが、その名前を持つコンピュータはネットワーク上に存在しません。このスクリプトの目的は、存在しないコンピュータ名をエラーのトラップによってファイルに書き込み、接続できなかったコンピュータ名のファイルを作成することです。また、このスクリプトには、接続可能な 2 台のコンピュータ (localhost を使用します) への接続も含めます。スクリプトは、図 1 のようになります。

図 1 トラップの追加

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 ファイルも作成されませんでした。つまり、トラップがまったく実行されていません。何が起こったのでしょうか。

fig02.gif

図 2 期待とは異なる出力

スクリプトを停止する

ここで重要なのは、シェルの通常のエラー メッセージが例外と同じものではないことです (エラーには、終了するエラーと終了しないエラーがあります。終了するエラーの場合、パイプラインの実行が停止し、例外が発生します)。トラップできるのは例外だけです。エラーが発生すると、シェルによって組み込みの $ErrorActionPreference 変数が参照され、処理が決定されます。この変数の既定値は "Continue" です。これは、"エラー メッセージを表示し、処理を続行する" ことを表しています。この変数の値を "Stop" に変更した場合、エラー メッセージが表示され、トラップ可能な例外が発生します。ただし、この変数の値を変更すると、スクリプト内でどのようなエラーが検出された場合でも、同じ動作が発生します。

より優れた手法は、問題の原因と思われるコマンドレットでのみ "Stop" の動作を使用することです。この処理は –ErrorAction (または –EA) パラメータという、すべてのコマンドレットでサポートされる共通のパラメータを使用して実行できます。図 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 パラメータを使用して得ることができるより優れた結果

トラップの最後で Continue を使用することによって、例外が発生した行に続くコード行から実行を再開するようシェルに指示できます。また、Break キーワードを使用することもできます (このキーワードについては後述します)。さらに、スクリプト内で定義されている $computer 変数がトラップ内でも有効であることに注目してください。これは、トラップがスクリプト自体の子スコープになっている、つまりトラップがスクリプト内のすべての変数を参照できるからです (スコープの詳細についても後述します)。

すべてはスコープの中に

Windows PowerShell のエラー トラップで特に厄介な点は、スコープの使用です。シェル自体はグローバル スコープを表し、シェル内で実行されるすべての処理が含まれます。スクリプトを実行すると、そのスクリプト独自のスクリプト スコープが適用されます。関数を定義すると、その関数内が独自のプライベート スコープになります。他のコードについても同様です。この結果、一種の親子関係を持つ階層が作成されます。

例外が発生すると、シェルによって現在のスコープ内のトラップが検索されます。つまり、関数内で例外が発生した場合は、関数内のトラップが検索されます。トラップが検出されると、シェルによってそのトラップが実行されます。トラップの最後が 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 行目のトラップが実行されます。トラップの最後が 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 行目で発生した場合、シェルによって、関数のスコープ内のトラップが検索されます。このスコープ内にはトラップが存在しないので、関数のスコープから抜けて、親スコープ内のトラップが検索されます。このスコープ内にはトラップが存在するので、1 行目が実行されます。この場合、Continue キーワードによって、同じスコープ内の例外の後にあるコード行、つまり 8 行目ではなく 12 行目から実行が再開されます。言い換えると、一度スコープを抜けた関数内の処理を再度実行することはありません。

では、この動作を次の例と比べてみましょう。

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 行目のトラップが実行されます。Continue キーワードでもこのスコープを抜けることなく、7 行目から実行が再開されます。これが、エラーの発生が予想されるスコープ内にトラップを含める利点です。エラーが発生した後もスコープ内にとどまり、そのスコープ内で実行を再開できます。では、この手法が適切でないシナリオの場合はどうすればよいでしょうか。

今月のコマンドレット: Compare-Object

このコマンドレットは、構成の基準を管理するうえで役立ちます。Compare-Object (エイリアスは Diff です) は、2 組のオブジェクトを互いに比較するように設計されています。既定では、各オブジェクトのすべてのプロパティが比較され、あらゆる差異が出力されます。では、要件にぴったり一致するようにサーバーのサービスを構成したとしましょう。この状況で、次のスクリプトを実行して基準を作成します。

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 行目でトラップされ、トラップが 9 行目で判断を行います。$condition が True の場合、トラップは 16 行目から処理を続行します。

ただし、$condition が False の場合、Break キーワードの処理が実行されます。これによって現在のスコープから抜け、元の例外が親スコープに渡されます。シェルの目線で見れば、19 行目で例外が発生し、1 行目で例外がトラップされたことになります。Continue キーワードによって、シェルの処理が 20 行目から再開されます。

実際には、どちらのトラップにも、もう少し多くのコードが記述し、それらのコード内でエラー処理やログ記録などの処理を行います。この例では、実際の流れをわかりやすくするために、そのような処理用のコードを省略しています。

心配は無用

どのような場合にエラーのトラップを実装すればよいかと言えば、それはエラーの発生が予想されていて、ファイルへのエラーの記録や、より役立つエラー メッセージの表示など、単純なエラー メッセージ以上の動作が必要な場合のみです。

私はたいてい、複雑なスクリプトにはエラー処理を含めて、発生が予想されるエラーを処理できるようにしています。このようなエラーには、接続やアクセス許可に関する問題が含まれますが、エラーはこれらに限定されません。

確かに、エラーのトラップについて理解するには、もう少し手間と時間をかける必要がありますが、その投資を惜しまなければ、Windows PowerShell 内でさらに複雑な処理を実行するようになったときに、より洗練された本格的なコードを作成できるでしょう。

Don Jones は『Windows PowerShell: TFM』の共著者であり、他にも IT に関連する多数の書籍を執筆しています。Don に対するお問い合わせについては、彼のブログ (concentratedtech.com) を参照してください。