C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2007/11/14 01:38

PowerShell の例外処理
PowerShell で try - catch - finally を実現

以前、この 2 つの記事で PowerShell の例外処理に関することを書きました。
で、一つ重大なことを見落としていました。Write-Error コマンドレットの存在です。
実は、Write-Error コマンドレットで出力した例外は trap ブロックで捕捉することができないようです。だから、try - catch -finally を実現する関数も、Write-Error コマンドレットの前では無力です。

ErrorActionPreference 変数 ( あるいは -errorAction 共通パラメータ ) の設定は反映されます。というより、trap ブロックが無力であるため完全にこれに従って動作します。
ErrorActionPreference 変数に "Stop" が設定されている場合、 Write-Error コマンドレットによって発生した例外を trap ステートメントにて捕捉でき ( るように見え ) ます。でもこれは、厳密には Write-Error コマンドレットによって発生した例外ではありません。ActionPreferenceStopException という別の例外です。残念ながら、この例外から本来発生した例外を取得することはできないようです。



Write-Error コマンドレットのヘルプを見ると、 「オブジェクトをエラー パイプラインに書き込みます。」 と書いてあります。
私なりに調べてみたところ、この記述は半分正しく半分間違っています。
Write-Error コマンドレットに限らず、 例外に ErrorActionPreference 変数の "Continue" が適用されると、最終的には例外が通知されるもののそれ以降の処理が続行されます。これを実現するための仕組みがエラーパイプラインです。
先ほど書いたように、Write-Error コマンドレットは ErrorActionPreference 変数の設定に完全に従います。Write-Error コマンドレットによって発生した例外がエラーパイプライン渡されるのは、あくまでも ErrorActionPreference 変数が "Continue" に設定されている場合のみです。
( なお、コマンドレット内で発生した例外の一部も、Write-Error と同様の動作をします。具体的には、エラー通知の実装が、例外のスローではなく Cmdlet クラスの WriteError メソッドを使用している場合です。 )



エラーパイプラインは、エラー情報を蓄積しながら処理を続行させるための仕組みです。ここからは、エラーパイプラインについて見て行きましょう。

エラーパイプラインは通常のパイプラインとは別モノです。例えば以下のコードを実行すると、"Hoge" だけがパイプラインで渡されていることが確認できます。 ( $ErrorActionPreference 変数は "Continue" ( 既定 ) で試してください。 )

&{ Write-Error "Error1"; "Hoge1"; } | %{ "$_ がパイプラインで渡されました。"; };


出力は以下の通りです。

Write-Error "Error1"; "Hoge1";  : Error1
発生場所 行:1 文字:2
+ &{ <<<<  Write-Error "Error1"; "Hoge1"; } | %{ "$_ がパイプラインで渡されました。"; };
Hoge1 がパイプラインで渡されました。

Error1 の情報が出力された後、 "Hoge1 がパイプラインで渡されました。" と出力されていることから、エラーパイプラインと通常のパイプラインが別モノだということがわかります。

では、以下のコードを実行するとどうなるでしょうか。

&{ Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ がパイプラインで渡されました。"; };


このコードを実行すると、以下のような出力が得られます。

Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2";  : Error1
発生場所 行:1 文字:2
+ &{ <<<<  Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ がパイプラインで渡されました。"; };
Hoge1 がパイプラインで渡されました。
Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2";  : Error2
発生場所 行:1 文字:2
+ &{ <<<<  Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ がパイプラインで渡されました。"; };
Hoge2 がパイプラインで渡されました。

どうも、エラーパイプラインと通常のパイプラインは別モノではあるものの、一連の順序は保持されているようです。
もしかすると、エラーパイプラインを渡っている例外もパイプラインを渡っているオブジェクトも、どこかで識別用のマークが付けられているだけで、一つのパイプラインを渡っているのかもしれません。まぁ、実際どうなっているのかまでは私にはわかりません。



エラーパイプラインにある例外を通常のパイプラインに移動させることもできます。それには、2>&1 という演算子を使います。
以下のコードを実行すると、エラーパイプラインにある例外が通常のパイプラインに移動することが確認できます。

(&{ Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ ok1"; }) 2>&1 | %{ "$_ ok2";};


出力は以下のようになります。

Error1 ok2
Hoge1 ok1 ok2
Error2 ok2
Hoge2 ok1 ok2

Error1 と Error2 には "ok2" だけが付加されています。つまり、2>&1 演算子によって通常のパイプラインに移動されたわけです。
ここでは結果をわかりやすくするために例外を文字列に変換しましたので、例外情報が赤文字で書き出されてはいません。通常のパイプラインに移動されても、文字列に変換したりせず例外がそのまま出力された場合は、赤文字で出力されます。
( 余談ですが、PS オブジェクトに対して、Add-Member コマンドレットを使って "writeErrorStream" というノートプロパティを追加し値を true に設定すると、そのオブジェクトの出力時に出力が赤文字になります。例外が赤文字で表示されるのはこの為です。 )



エラーパイプラインはこんなところでしょうか。ちなみに通常のパイプラインからエラーパイプラインに移動させる 1>&2 演算子というものもあるようですが、使おうとすると「まだサポートされていない」と言われます。次期バージョンである PowerShell 2.0 ではサポートされるのかもしれません。
スポンサーサイト