TL;DR
PowerShellのエラーハンドリングには、Terminating Error
とNon-Terminating Error
とに対するものがある。
try-catch
による処理の仕方や、Non-Terminating Error
の起こし方、これの捕まえ方などのサンプルコードをメモっておく。
はじめに
以前の投稿(PowerShellで例外を捕まえられない)で、PowerShellを実行したときに出る例外(正確には「例外っぽいエラー出力」)を捕まえるにはErrorAction
パラーメタを渡すか$ErrorActionPreference
変数で制御するかすればよい的なことを書いたけど、今回はもう少し丁寧にどういうことなのか調べてみた。
エラーの種類
Terminating Error
とNon-Terminating Error
がある。カンタンにいうと、
Terminating Error
は「発生すると処理がそこで中断する」エラーで、catchできる。
Non-Terminating Error
は「発生しても処理は継続し、エラー自体は出力される」エラーで、catchできない。
この詳細は、"Hey, Scripting Guy! Blog"のUnderstanding Non-Terminating Errors in PowerShellという記事に乗ってるので、「もっと」という方はそちらを参照されたし。
また、PowerShell Documentationのabout_try_catch_finallyにも説明があるので、スペック至上主義のかたはそちらを(Get-Help about_try_catch_finally
でも読めるかも)。
サンプルコードとともに
エラーを起こす関数を有するterminate_or_not.ps1
と、これをテスト実行するterminate_or_not.Tests.ps1
を作成し挙動を確認する。
terminate_or_not.ps1
のInvoke-TerminatingError
は、Terminating Error
を起こす(いきなりthrowしている)。また、Invoke-NonTerminatingError
は、Non-Terminating Error
を起こす(いきなりWrite-Error
している)。さらに、Invoke-NonTerminatingErrorEx
はInvoke-NonTerminatingError
と同じだけど、いわゆる「高度な関数」としているのでこれらは違った扱いをすることができる。具体的にはErrorAction
パラメータによる制御を受け付ける。
これらの関数を実行するテストコードterminate_or_not.Tests.ps1
にて、例外ハンドリングを試みるIt
を実行してみる。
まずは、terminate_or_not.ps1
を以下に示す。
function Invoke-TerminatingError {
<#
.SYNOPSIS
TerminatingなErrorを起こします
#>
Write-Debug "throwはterminating errorなので、キャッチできる"
throw "だめぽ(throw)"
}
function Invoke-NonTerminatingError {
<#
.SYNOPSIS
Non-TerminatingなErrorを起こします
#>
Write-Debug "Write-ErrorはNon-Terminating Errorなのでキャッチできない"
Write-Error "だめぽ(Write-Error)"
}
function Invoke-NonTerminatingErrorEx {
<#
.SYNOPSIS
Non-TerminatingなErrorを起こします
但し、Functions AdvancedなのでErrorActionで制御可能
#>
[CmdletBinding()]
Param(
)
Write-Debug "Write-ErrorはNon-Terminating Errorなのでキャッチできない(Function Advanced)"
Write-Error "だめぽ(Write-Error)"
}
続いて、テストコードであるterminate_or_not.Tests.ps1
を示す。テストはPester
を使用。
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand) -replace "\.Tests\.", "."
. (Join-Path $here $sut)
Describe "Terminating Errorと Non-Terminating Errorの比較" {
It "Terminating Errorを吐く関数を実行するとtry-catchで捕まえられるテスト" {
$reached = $false
$caught = $false
try {
Write-Verbose "エラーによりcatchに遷移するはず"
Invoke-TerminatingError
$reached = $true
}
catch {
$caught = $true
}
$reached | Should Be $false
$caught | Should Be $true
}
It "Non-Terminating Errorを吐く関数を実行するとtry-catchで捕まえられないテスト" {
$reached = $false
$caught = $false
try {
Write-Verbose "エラー出力は表示されるがcatchに遷移しないはず"
Invoke-NonTerminatingError
$reached = $true
}
catch {
$caught = $true
}
$reached | Should Be $true
$caught | Should Be $false
}
BeforeEach {
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
}
}
Describe "Non-Terminating Errorを吐く普通の関数と高度な関数でErrorAction設定時の比較" {
It "普通の関数にErrorActionを指定できないテスト" {
$reached = $false
$caught = $false
try {
Write-Verbose "ErrorActionは引数として受取不可だが、Non-Terminating Errorのためcatchできない。"
Write-Verbose "また、テスト対象自体もNon-Terminating Errorなのでcatchできず、エラー表示される。"
Invoke-NonTerminatingError -ErrorAction "Stop"
$reached = $true
}
catch {
$caught = $true
}
$reached | Should Be $true
$caught | Should Be $false
}
It "高度な関数にErrorActionを指定してcatchに遷移するテスト" {
$reached = $false
$caught = $false
try {
Write-Verbose "ErrorActionでNon-Terminating Errorを停止し、catchに遷移する。"
Invoke-NonTerminatingErrorEx -ErrorAction "Stop"
$reached = $true
}
catch {
$caught = $true
}
$reached | Should Be $false
$caught | Should Be $true
}
BeforeEach {
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
}
}
実行結果はこんなかんじ。
PS D:\tmp> Invoke-Pester
Executing all tests in '.'
Executing script D:\tmp\terminate_or_not.Tests.ps1
Describing Terminating Errorと Non-Terminating Errorの比較
VERBOSE: エラーによりcatchに遷移するはず
DEBUG: throwはterminating errorなので、キャッチできる
[+] Terminating Errorを吐く関数を実行するとtry-catchで捕まえられるテスト 63ms
VERBOSE: エラー出力は表示されるがcatchに遷移しないはず
DEBUG: Write-ErrorはNon-Terminating Errorなのでキャッチできない
Invoke-NonTerminatingError : だめぽ(Write-Error)
At D:\tmp\terminate_or_not.Tests.ps1:28 char:13
+ Invoke-NonTerminatingError
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Invoke-NonTerminatingError
[+] Non-Terminating Errorを吐く関数を実行するとtry-catchで捕まえられないテスト 22ms
Describing Non-Terminating Errorを吐く普通の関数と高度な関数でErrorAction設定時の比較
VERBOSE: ErrorActionは引数として受取不可だが、Non-Terminating Errorのためcatchできない。
VERBOSE: また、テスト対象自体もNon-Terminating Errorなのでcatchできず、エラー表示される。
DEBUG: Write-ErrorはNon-Terminating Errorなのでキャッチできない
Invoke-NonTerminatingError : だめぽ(Write-Error)
At D:\tmp\terminate_or_not.Tests.ps1:53 char:13
+ Invoke-NonTerminatingError -ErrorAction "Stop"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Invoke-NonTerminatingError
[+] 普通の関数にErrorActionを指定できないテスト 35ms
VERBOSE: ErrorActionでNon-Terminating Errorを停止し、catchに遷移する。
DEBUG: Write-ErrorはNon-Terminating Errorなのでキャッチできない(Function Advanced)
[+] 高度な関数にErrorActionを指定してcatchに遷移するテスト 10ms
Tests completed in 131ms
Tests Passed: 4, Failed: 0, Skipped: 0, Pending: 0, Inconclusive: 0
全部成功した。
説明を、と思ったけど、It
文見てもらったほうが早い気がするので割愛。