LoginSignup
8
5

More than 5 years have passed since last update.

PowerShellでのエラー/例外処理について

Posted at

TL;DR

PowerShellのエラーハンドリングには、Terminating ErrorNon-Terminating Errorとに対するものがある。
try-catchによる処理の仕方や、Non-Terminating Errorの起こし方、これの捕まえ方などのサンプルコードをメモっておく。

はじめに

以前の投稿(PowerShellで例外を捕まえられない)で、PowerShellを実行したときに出る例外(正確には「例外っぽいエラー出力」)を捕まえるにはErrorActionパラーメタを渡すか$ErrorActionPreference変数で制御するかすればよい的なことを書いたけど、今回はもう少し丁寧にどういうことなのか調べてみた。

エラーの種類

Terminating ErrorNon-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.ps1Invoke-TerminatingErrorは、Terminating Errorを起こす(いきなりthrowしている)。また、Invoke-NonTerminatingErrorは、Non-Terminating Errorを起こす(いきなりWrite-Errorしている)。さらに、Invoke-NonTerminatingErrorExInvoke-NonTerminatingErrorと同じだけど、いわゆる「高度な関数」としているのでこれらは違った扱いをすることができる。具体的にはErrorActionパラメータによる制御を受け付ける。
これらの関数を実行するテストコードterminate_or_not.Tests.ps1にて、例外ハンドリングを試みるItを実行してみる。

まずは、terminate_or_not.ps1を以下に示す。

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を使用。

terminate_or_not.Tests.ps1

$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"
    }
}

実行結果はこんなかんじ。

console

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文見てもらったほうが早い気がするので割愛。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5