例を使ってPowerShellのTry Catchブロックをマスターする

以下のような赤い壁のテキストのスクリプトやPowerShellのコマンドレットを実行したことはありますか?

Example of errors in PowerShell

エラーは圧倒的で混乱をもたらすことがあります。そして、エラーは読みにくいことが多いため、スクリプトのどこで何が間違っているのかを判断することはほぼ不可能です。

幸いなことに、PowerShellにはエラーハンドリングを通じてこれを改善するためのいくつかのオプションがあります。エラーハンドリングを使用すると、エラーをフィルタリングして表示することができ、理解しやすくなります。そして、エラーを理解することで、エラーハンドリングにさらなるロジックを追加することも簡単です。

この記事では、PowerShellでのエラーとエラーハンドリングを実行するためのPowerShellのTry Catchブロック(およびfinallyブロック)について学びます。

PowerShellでエラーがどのように動作するかを理解する

エラーハンドリングに入る前に、まずPowerShellのエラーに関するいくつかの概念を理解しましょう。エラーの理解は、より良いエラーハンドリング戦略につながることがあります。

$Error自動変数

PowerShellでは、多くの自動変数がありますが、その1つが$Error自動変数です。PowerShellはセッションでエンカウントしたすべてのエラーを$Error変数に格納します。$Error変数は最新のエラーをソートしたエラーの配列です。

最初にPowerShellセッションを開くと、$Error変数は空になります。 $Error変数を呼び出すことで、それを確認できます。

The $Error variable is empty

上記のように、$Error変数は空のままです。しかし、エラーが生成されると、エラーは$Error変数に追加されて保存されます。

以下の例では、存在しないサービス名を意図的に取得することでエラーが生成されます。

PS> Get-Service xyz
PS> $Error
PS> $Error.Count
The error is added to the $Error variable

上記の出力からわかるように、生成されたエラーは$Error変数に追加されました。

$Error変数には、PowerShellセッションで生成されたエラーのコレクションが格納されています。各エラーは、配列の位置を呼び出すことでアクセスできます。最新のエラーは常にインデックス0にあります。

たとえば、最新のエラーは$Error[0]を使用して取得できます。

$Errorオブジェクトのプロパティ

PowerShellではすべてがオブジェクトであるため、$Error変数もオブジェクトであり、オブジェクトにはプロパティがあります。 $Error変数をGet-Memberコマンドレットにパイプすると、利用可能なプロパティのリストが表示されます。

$Error | Get-Member
The $Error object properties

エラーの原因を特定するには、以下のコマンドを使用してInvocationInfoプロパティの内容を表示できます。

$Error[0].InvocationInfo
The InvocationInfo property

同じように他のプロパティでも同様に操作し、他にどのような情報を見つけることができるかを発見できます!

終了エラー

終了エラーは、PowerShellが非終了エラーとは異なり、実行フローを停止します。終了エラーが発生する方法はいくつかあります。その一つの例は、存在しないパラメータを使用してコマンドレットを呼び出す場合です。

以下のスクリーンショットからわかるように、Get-Process notepadというコマンドが実行されると、コマンドは有効であり、notepadプロセスの詳細が表示されます。

The notepad process details

しかし、Get-Process notepad -handle 251のように存在しないパラメータを使用すると、コマンドレットはhandleパラメータが無効であるというエラーを表示します。その後、notepadプロセスの詳細を表示せずにコマンドレットが終了します。

Error is thrown because the parameter is invalid

非終了エラー

非終了エラーは、スクリプトやコマンドの実行を停止しないエラーです。たとえば、以下のコードをご覧ください。このコードはfileslist.txtファイルからファイル名のリストを取得します。その後、スクリプトは各ファイル名を処理し、各ファイルの内容を読み取り、画面に出力します。

$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
    Get-Content $file
}

filelist.txtファイルの内容は以下のリストに表示されています。

File_1.log
File_2.log
File_3.log
File_4.log
File_5.log
File_6.log
File_7.log
File_8.log
File_9.log
File_10.log

しかし、File_6.logが実際に存在しなかった場合はどうでしょうか?コードを実行すると、スクリプトがFile_6.logを見つけることができないため、エラーが発生することが予想されます。以下のような出力が表示されます。

Example of non-terminating error

上記の結果のスクリーンショットからわかるように、スクリプトはリスト内の最初の5つのファイルを読み取ることができましたが、ファイルFile_6.txtを読み取ろうとしたときにエラーが発生しました。スクリプトはその後、終了する前に残りのファイルを読み取り続けました。スクリプトは終了しませんでした。

これまでに、終了するエラーと終了しないエラーについて学びましたが、終了しないエラーを終了するエラーとして扱うこともできることをご存知でしょうか?

PowerShellには、参照変数という概念があります。これらの変数は、PowerShellの振る舞いをさまざまな方法で変更するために使用されます。これらの変数の1つが$ErrorActionPreferenceと呼ばれます。

$ErrorActionPreference変数は、PowerShellが終了しないエラーをどのように処理するかを変更するために使用されます。デフォルトでは、$ErrorActionPreferenceの値はContinueに設定されています。$ErrorActionPreference変数の値をSTOPに変更すると、PowerShellはすべてのエラーを終了するエラーとして扱います。

$ErrorActionPreferenceの値を変更するために、以下のコードを使用してください。

$ErrorActionPreference = "STOP"

他の有効な$ErrorActionPreference変数の値については、PowerShell ErrorActionPreferenceを参照してください。

さて、この記事の非終了エラーセクションで使用された例に戻りましょう。次のコードのようにスクリプトを変更して、$ErrorActionPreferenceの変更を含めることができます:

# $ErrorActionPreferenceの値をSTOPに設定する
$ErrorActionPreference = "STOP"
$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
    Get-Content $file
}

上記の変更されたコードを実行すると、$ErrorActionPreferenceの値がデフォルト値のContinueに設定されている場合とは異なる動作をします。

Forcing a terminating error using the $ErrorActionPreference variable

上記の結果のスクリーンショットからわかるように、スクリプトはリスト内の最初の5つのファイルを読み取ることができましたが、ファイルFile_6.txtの読み取り時にファイルが見つからないためエラーが返されます。その後、スクリプトが終了し、残りのファイルは読み取られません。

$ErrorActionPreferenceの値は現在のPowerShellセッションでのみ有効です。新しいPowerShellセッションが開始されると、デフォルト値にリセットされます。

ErrorAction共通パラメータ

$ErrorActionPreferenceの値がPowerShellセッションに適用されると、共通パラメータをサポートする任意のcmdletにErrorActionパラメータが適用されます。ErrorActionパラメータは、$ErrorActionPreference変数と同じ値を受け入れます。

ErrorActionパラメータの値が$ErrorActionPreferenceの値より優先されます。

前の例と同じコードを使用して戻りましょう。ただし、今回はGet-Content行にErrorActionパラメータが追加されます。

# $ErrorActionPreferenceの値をデフォルト(CONTINUE)に設定
$ErrorActionPreference = "CONTINUE"
$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
		# -ErrorAction共通パラメータを使用
		Get-Content $file -ErrorAction STOP
}

変更したコードを実行すると、$ErrorActionPreferenceContinueに設定されていても、スクリプトがエラーに遭遇すると終了します。スクリプトが終了するのは、Get-ContentのPowerShell ErrorActionパラメータの値がSTOPに設定されているためです。

Forcing a terminating error using the PowerShell ErrorAction parameter

PowerShellのTry Catchブロックの使用

ここまで、PowerShellのエラーや$ErrorActionPreference変数とPowerShellのErrorActionパラメータの動作について学びました。さあ、いよいよ面白い部分であるPowerShellのTry Catch Finallyブロックについて学びましょう。

PowerShellのtry catchブロック(およびオプションのfinallyブロック)は、コードの範囲を囲んで返されるエラーをキャッチする方法です。

以下のコードは、Tryステートメントの構文を示しています。

try {
    <statement list>
}
catch [[<error type>][',' <error type>]*]{
    <statement list>
}
finally {
    <statement list>
}

Tryブロックには、PowerShellによってエラーを「試し」て監視するコードが含まれています。 Tryブロックのコードがエラーに遭遇すると、エラーは$Error変数に追加され、Catchブロックに渡されます。

Catchブロックには、Tryブロックからエラーを受け取った場合に実行するアクションが含まれています。 Tryステートメントには複数のCatchブロックがある場合があります。

Finallyブロックには、Tryステートメントの最後に実行されるコードが含まれています。このブロックはエラーが発生したかどうかに関係なく実行されます。

PowerShell ErrorActionを使用して非特定のエラー(キャッチオール)をキャッチする

A simple Try statement contains a Try and a Catch block. The Finally block is optional.

たとえば、非特定の例外をキャッチするには、Catchパラメータは空にする必要があります。以下の例のコードは、$ErrorActionPreference変数セクションで使用されたスクリプトと同じものですが、Try Catchブロックを使用するように変更されています。

以下のコードからわかるように、今回はforeachステートメントTryブロック内に含まれています。その後、Catchブロックには、エラーが発生した場合に文字列「An Error Occurred」を表示するコードが含まれています。 Finallyブロック内のコードは、$Error変数をクリアするだけです。

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch {
    Write-Host "An Error Occured" -ForegroundColor RED
}
finally {
    $Error.Clear()
}

上記のコードは、PowerShellで実行すると、以下に示す出力が表示されます。

Script terminated when an error occurred

上記の出力は、スクリプトがエラーに遭遇し、Catchブロック内のコードを実行してから終了したことを示しています。

エラーは処理され、それがエラーハンドリングのポイントでした。ただし、表示されるエラーはあまりにも一般的でした。より詳細なエラーを表示するには、Tryブロックによって渡されたエラーのExceptionプロパティにアクセスすることができます。

以下のコードは、特にCatchブロック内のコードを修正し、パイプラインから渡された現在のエラーの例外メッセージを表示するように変更されています – $PSItem.Exception.Message

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch {
    Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
    $Error.Clear()
}

今回、修正されたコードを実行すると、表示されるメッセージはより詳細になります。

Script terminated with a descriptive error message

特定のエラーのキャッチ

キャッチオールのエラーハンドリングが最も適切なアプローチではない場合もあります。おそらく、スクリプトがエンカウントしたエラーのタイプに依存するアクションを実行したいと思うかもしれません。

エラータイプをどのように決定しますか?直近のエラーのExceptionプロパティのTypeName値をチェックすることで判断できます。たとえば、前の例からエラータイプを見つけるには、次のコマンドを使用します:

$Error[0].Exception | Get-Member

上記のコードの結果は、以下のスクリーンショットのようになります。TypeNameの値が表示されていることがわかります – System.Management.Automation.ItemNotFoundException

Getting the error TypeName value

エラータイプがわかったので、特定のエラーをキャッチするためにコードを変更してください。以下の修正後のコードからわかるように、2つの Catch ブロックがあります。最初の Catch ブロックは特定のエラータイプ (System.Management.Automation.ItemNotFoundException) をキャッチします。一方、2番目の Catch ブロックには一般的な、キャッチオールのエラーメッセージが含まれています。

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch [System.Management.Automation.ItemNotFoundException]{
    Write-Host "The file $file is not found." -ForegroundColor RED
}
catch {
    Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
    $Error.Clear()
}

以下のスクリーンショットは、上記の修正後のコードの出力例です。

Script terminated with a specific error message

結論

この記事では、PowerShell のエラー、そのプロパティ、およびエラーの特定のタイプを判断する方法について学びました。また、$ErrorActionPreference 変数と PowerShell の ErrorAction パラメータが非終了エラーの扱いにどのように影響するかの違いも学びました。

また、PowerShell の Try Catch Finally ブロックを使用して、特定のエラーやキャッチオールのアプローチによるエラーハンドリングを行う方法も学びました。

この記事で示されている例は、Try Catch Finally ブロックの基本的な動作を示すものです。この記事で得た知識を活用して、スクリプトでエラーハンドリングを開始するための出発点として活用してください。

さらなる情報

Source:
https://adamtheautomator.com/powershell-try-catch/