PowerShellで発生したエラーの型によって処理を分けたり型を指定してエラーを発生させる方法
2021/01/28
本エントリーの目次
PowerShellで発生したエラーの型によって、異なる処理を実行したい!
PowerShellスクリプトを実行した際、何らかの要因によって実行時エラーが発生してしまうことがあります。
そしてエラーが起きた場合には、専用の処理を行いたい!なんてことが少なくありません。
こういったケースでは、以下のようなPowerShellの『try catch finally』構文を使うことで対応できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | try{ #何らかのエラーが発生する可能性のある処理を記述 }catch{ #何らかのエラーが発生したときに行う処理を記述 }finally{ #エラーの発生有無を問わず、try{}とcatch{}の処理が終わった後に実行する処理を記述 } |
上記『try catch finally』構文を使うことで、try{}部に記載されていた処理を実行中にエラーが発生した場合、catch{}部に記載されている処理が実行されます。
finally{}部の処理は、try{}部に記載されていた処理を実行中に、エラーが発生した場合と起きなかった場合の両方で実行されます。
そのため、リソースの開放処理に代表される終了処理のようなものを記述するケースが多いでしょう。
また、終了処理のようなものが不要である場合には、finally{}部の記述は必要ありません。
先ほど例示した構文では、すべてのエラーについて、共通してcatch{}部の処理が実行されます。
ですがPowerShellスクリプトで行う処理によっては、エラーの内容、特に発生したエラーの型に応じて異なるcatch{}部の処理を実行したい!
なんてケースもあるでしょう。
そこで今回は、PowerShellで発生したエラーの型に応じて処理を分岐実行する方法や、型を指定してエラーを発生させる方法をご紹介します!
PowerShellで発生したエラーの型に応じて、処理を分岐実行(異なるcatch{}部の処理を実行)する方法
PowerShellで特定の型のエラーの発生を検出(catch)し、処理を分岐実行したい場合には、catch{}部とは別に、catch [<catchしたいエラーの型の名前>]{}という構文の処理を追記・用意します。
たとえばPowerShellでは、数字をゼロ除算(0で除算)した場合、『System.DivideByZeroException』という型のエラーが発生します。
この型のエラーが発生した場合には、その他のエラーとは異なる処理を実行したいというケースでは、以下のような記述を行うと良いでしょう。
1 2 3 4 5 6 7 8 9 10 11 | try{ $Result = 3 / 0 }catch [System.DivideByZeroException]{ Write-Host "System.DivideByZeroExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
上記処理を実行すると、以下のような結果が取得されます。
1 2 | System.DivideByZeroExceptionが発生しました。 finally部の処理です。 |
したがって、『System.DivideByZeroException』という型のエラーが発生したときの”専用の”処理は、上記処理の6行目の部分に記述すれば良い、というわけです。
特定の複数のエラーの型について、異なる処理を実行する方法
分岐したいエラーの型が一つではなく複数ある場合には、1つの場合と同様の構文を複数記述することで対応します。
このブログでは以前に、PowerShellを使ってインターネットからファイルをダウンロードする方法の一つとして、『Invoke-WebRequest』コマンドレットをご紹介しています。
参考:PowerShellを使ってファイルをインターネットからダウンロードする方法
そして『Invoke-WebRequest』コマンドレットは実行時に、『System.Net.WebException』や『System.IO.IOException』という型のエラーを発生させることがあります。
前者は、ダウンロード対象のファイルのURLが間違っていて存在しないURLであったときなどに。
後者は、保存先として指定したパスに既に同名のファイルが存在し、他のプロセスがそのファイルをロックしているケースなどで発生します。
これらの型のエラーについて、それぞれ個別に処理を実行するときは、以下のような記述を行うというわけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 | try{ Invoke-WebRequest "https://www.haruru29.net/blog/wp-content/uploads/2018/12/corsair-ml120-pro-led.jpg" -OutFile "C:\sample\corsair-ml120-pro-led.jpg" }catch [System.Net.WebException]{ Write-Host "System.Net.WebExcptionが発生しました。" }catch [System.IO.IOException]{ Write-Host "System.IO.IOExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
上記処理の実行時に、ダウンロード対象のファイルのURLが存在しないURLであった場合、以下の結果が取得されます。
1 2 | System.Net.WebExcptionが発生しました。 finally部の処理です。 |
また、保存先として指定したパスに既に同名のファイルが存在し、他のプロセスがそのファイルをロックしているケースでは、以下のような結果が取得されます。
1 2 | System.IO.IOExceptionが発生しました。 finally部の処理です。 |
特定の複数のエラーの型について、同じ処理を実行する方法
先にご紹介した方法では、『Invoke-WebRequest』コマンドレットの実行時に発生する『System.Net.WebException』と『System.IO.IOException』について、異なる処理が実行されます。
ですがこれら2つの型のエラーについて、同様の処理を実行(同じ分岐の処理を実行)したい!といったケースも考えられます。
こういった場合には、以下のようにcatch [<catchしたいエラーの型の名前 1>],[<catchしたいエラーの型の名前 2>]{}という構文で処理を記述してください。
1 2 3 4 5 6 7 8 9 10 11 | try{ Invoke-WebRequest "https://www.haruru29.net/blog/wp-content/uploads/2018/12/corsair-ml120-pro-led.jpg" -OutFile "C:\sample\corsair-ml120-pro-led.jpg" }catch [System.Net.WebException],[System.IO.IOException]{ Write-Host "System.Net.WebExcption、またはSystem.IO.IOExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
このPowerShellスクリプトでは、『Invoke-WebRequest』コマンドレットを実行した際に『System.Net.WebException』、または『System.IO.IOException』が発生した場合、どちらも以下の結果が取得されます。
1 2 | System.Net.WebExcption、またはSystem.IO.IOExceptionが発生しました。 finally部の処理です。 |
基底(継承元)クラスでのキャッチも可能!
PowerShellでエラーが発生した際、発生したエラーの型の基底(継承元)クラスの型でキャッチ(エラーを検出)することも可能です。
たとえば『System.IO.IOException』の基底クラスは『System.SystemException』です。
参考:Microsoft Docs – IOException クラス
したがって『catch [System.IO.IOException]{}』と書いている箇所を、『catch [System.SystemException]{}』に変更。
その後、System.IO.IOExceptionの型のエラーが発生した場合、catch [System.SystemException]{}部の処理が実行されます。
ただしこの場合には『System.ArgumentException』など、『System.SystemException』の他の派生クラスも同様にキャッチされ、catch [System.SystemException]{}部の処理が実行されるようになります。
internalなクラスは、publicな基底クラスでキャッチする
PowerShellの実行時に発生する例外の中には、internalなクラスのものも存在します。
たとえば、DNSサーバーに問い合わせを行い、DNSレコードの内容を取得することができる『Resolve-DnsName』コマンドレットでは、対象のドメインに誤って$nullを指定した場合、『System.Management.Automation.ParameterBindingValidationException』という型のエラーを発生させます。
参考:WindowsのDNS(名前解決)キャッシュのクリア方法やキャッシュさせない方法
そのためキャッチ部について、『catch [System.Management.Automation.ParameterBindingValidationException]{}』と書きたいところ。
ですがこのように記述した場合、『型 [System.Management.Automation.ParameterBindingValidationException] が見つかりません。』というエラーが発生します。
『System.Management.Automation.ParameterBindingValidationException』は、『System.Management.Automation.ParameterBindingException』のinternalな派生クラスであり、これをキャッチしたい場合には以下のように、publicな基底クラスの『System.Management.Automation.ParameterBindingException』でキャッチする必要があります。
1 2 3 4 5 6 7 8 9 10 11 | try{ Resolve-DnsName -Name $null }catch [System.Management.Automation.ParameterBindingException]{ Write-Host "System.Management.Automation.ParameterBindingExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
上記処理の実行結果は、以下のとおりです。
1 2 | System.Management.Automation.ParameterBindingExceptionが発生しました。 finally部の処理です。 |
尚、catch [System.Management.Automation.ParameterBindingException]{}部で、『$Error[0].CategoryInfo.Reason』の内容を参照することで、『ParameterBindingValidationException』を参照することが可能です。
エラーが発生してもキャッチできないケースの対応方法
ここまでにご紹介した方法だけでは、エラーが発生してもキャッチできないケースも存在します。
たとえば以下のような処理があったとしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 | try{ "追記データです" | Add-Content -Path "C:\sample\test.txt" Write-Host "try部の最後の処理です。" }catch [System.IO.IOException]{ Write-Host "System.IO.IOExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
上記処理を実行する際、『C:\sample\test.txt』をExcelで開くなどし、他のプロセスによってファイルがロックされている状態であった場合、期待されるのは白文字での以下のようなコンソールへの出力でしょう。
1 2 | System.IO.IOExceptionが発生しました。 finally部の処理です。 |
しかし実際に実行してみると、以下のような結果が白文字で取得されるはずです。
1 2 | try部の最後の処理です。 finally部の処理です。 |
さらに上記出力だけではなく、赤字で『Add-Content : 別のプロセスで使用されているため、プロセスはファイル ‘C:\sample\test.txt’ にアクセスできません。…』というようなエラー文字列も画面上に出力されるはずです。
したがってtry{}部の処理を中断してcatch [System.IO.IOException]{}部の処理を実行する。
という期待された動作ではなく、そのままtry{}部の次の処理が実行されています。
こういったケースでは以下のように、『-ErrorAction Stop』パラメーターを併用することで、想定通りの動きにできる場合があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | try{ "追記データです" | Add-Content -Path "C:\sample\test.txt" -ErrorAction Stop Write-Host "try部の最後の処理です。" }catch [System.IO.IOException]{ Write-Host "System.IO.IOExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
PowerShellで型を指定してエラーを発生させる方法
PowerShellで型を指定してエラーを発生させる場合には、『throw New-Object <発生させるエラーの型の名前>』という構文のコマンドを実行します。
『System.IO.IOException』を意図的に発生させ、それを検出して個別に処理を行うケースでは、以下のようなコマンドを実行すると良いでしょう。
1 2 3 4 5 6 7 8 9 10 11 | try{ throw New-Object System.IO.IOException }catch [System.IO.IOException]{ Write-Host "System.IO.IOExceptionが発生しました。" }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
また、型を指定せずに文字列を使ってエラーを発生させるケースもありますが、この場合にはたとえば『$Error[0].Exception.Message』を参照することで、その文字列を参照可能です。
1 2 3 4 5 6 7 8 9 10 | try{ throw "文字列を使ってThrowします!" }catch{ Write-Host "予期しないエラーが発生しました。" Write-Host ("エラーメッセージ:" + $Error[0].Exception.Message) }finally{ Write-Host "finally部の処理です。" } |
上記処理の結果は、以下のとおりです。
1 2 3 | 予期しないエラーが発生しました。 エラーメッセージ:文字列を使ってThrowします! finally部の処理です。 |
型を指定し、文字列を使ってエラーを発生させることも可能です。
たとえば、『別のプロセスで使用されているため、プロセスはファイルにアクセスできません。』という文字列で『System.IO.IOException』の型のエラーを意図的に発生させ、それをキャッチするケースでは、以下のように処理を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 | try{ throw New-Object System.IO.IOException("別のプロセスで使用されているため、プロセスはファイルにアクセスできません。") }catch [System.IO.IOException]{ Write-Host "System.IO.IOExceptionが発生しました。" Write-Host ("エラーメッセージ:" + $Error[0].Exception.Message) }catch{ Write-Host "予期しないエラーが発生しました。" }finally{ Write-Host "finally部の処理です。" } |
上記処理の結果は、以下のとおりです。
1 2 3 | System.IO.IOExceptionが発生しました。 エラーメッセージ:別のプロセスで使用されているため、プロセスはファイルにアクセスできません。 finally部の処理です。 |
以上、参考になさってくださーい!