PowerShellのエラー処理と再実行設計 ── try/catchが効かない罠からexit code・リトライの定石まで

· 更新日: · · PowerShell, Windows, エラー処理, リトライ, 自動化, 運用改善, スクリプト, タスクスケジューラ

更新履歴(4件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
分類の軸を1行で整理しました(公式の3カテゴリーは止まる範囲の軸で、この記事は`try`/`catch`に入るかの2分類を主軸に使う、という関係です)。「冪等」の初出に説明を加え、ステートメント終了エラーの具体例と最小コードを追加し、`$PSNativeCommandUseErrorActionPreference`の実験的機能名と5.1では変数自体が存在しないことを本文に格上げしました。
エラーの分類の説明が不正確でした。まず非終了エラーと終了エラーの2種類に分かれ、終了エラーがさらに分かれる、という関係に直しました。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21590129)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「PowerShellのエラー処理と再実行設計 ── try/catchが効かない罠からexit code・リトライの定石まで」合同会社小村ソフト. https://comcomponent.com/blog/powershell-error-handling-retry-design/

DOI(登録済みアーカイブ)
10.5281/zenodo.21590129
DOI(前回登録した版)
10.5281/zenodo.21733077

「try/catchを書いたのにcatchに入らない」「夜間バッチが失敗したのに、タスクスケジューラでは成功(0x0)になっている」「ネットワークの瞬断で、ときどき処理が落ちる」。PowerShellを無人運用に載せると、この3つは別々に対策する必要があります。

失敗を捕まえること、外部へ失敗を伝えること、安全に再実行することは、それぞれ別の設計です。赤いメッセージが出たことも、catchに入ったことも、それだけでは「運用側が失敗を検知できる」保証になりません。

この記事では、社内の定型処理を自動化する情シス担当者・開発者に向けて、エラーの分類、ErrorRecordの読み方、外部EXEの終了コード、無人実行の骨格、リトライの順に整理します。手元で動くスクリプトを、失敗時にも扱えるスクリプトへ変えるための話です。

1. まず結論

  • 捕まえる: try/catchが扱うのは終了エラーです。コマンドレットの非終了エラーも捕まえたい行には -ErrorAction Stop を明示します。外部EXEの非ゼロ終了コードは既定では別扱いなので、$LASTEXITCODE で判定します。1
  • 伝える: catchでは $_(ErrorRecord)から原因と位置を記録し、後始末はfinallyへ置きます。無人実行の入口では exit の値を決め、pwsh -File / powershell.exe -File で外部へ返します。ログだけ残して正常終了しないことが重要です。23
  • やり直す: リトライは一時的エラー限定・上限つき・冪等が三原則です。冪等(べきとう)とは、同じ処理を繰り返しても結果が変わらない性質です。間隔を指数バックオフで広げるだけでなく、二重取り込みや二重更新を起こさない設計を先に用意します。

困っている場所から読むなら、次の順で確認してください。

知りたいこと 読む場所
なぜcatchに入らないのか 2章: 2種類のエラーとStopの指定
catchで何を残し、どこまで処理するか 3章: ErrorRecordとfinally
robocopyやgitの失敗をどう判定するか 4章: 終了コードと5.1/7の違い
タスクスケジューラへ成否をどう返すか 5章: 起動方法とexit code
どこを、何回まで再実行してよいか 6章: リトライの条件と共通関数

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全18件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. 2種類のエラー ── なぜtry/catchが効かないのか

「catchできるか」と「どこまで止まるか」を分ける

まずは非終了エラーと終了エラーの2種類で考えると、try/catchの判断がしやすくなります。終了エラーをさらにステートメント終了とスクリプト終了に分けると、公式ドキュメントの3カテゴリーになります。数え方が矛盾しているのではなく、分類の軸が違います。1

分類 既定での止まり方 try内で発生した場合
非終了エラー 問題を報告し、後続の入力や処理を続ける そのままではcatchに入らない
終了エラー: ステートメント終了 現在の文・パイプラインを止める。捕捉されず、設定で挙動を変えていなければ次の文へ進む catchで扱える
終了エラー: スクリプト終了 呼び出しスタックを巻き戻す catchで扱える。未処理ならスクリプトを終了する

この表は「止まる範囲」の整理であり、エラーの重大さの順位ではありません。また、catchへ移った場合は、失敗した行の直後へ戻ってtryの続きを実行するわけではありません。12

非終了エラーは、Stopを付けて捕まえる

Get-Content や Get-ChildItem が個々の入力の処理に失敗すると、非終了エラーになることがあります。既定のContinueでは、赤いメッセージが出ても処理は続き、catchにもtrapにも入りません。次の例は、この既定値で実行した場合の違いです。14

# 【罠】catchには一度も入らず、"完了" まで表示される
try {
    Get-Content -Path 'C:\Data\存在しないファイル.txt'   # 非終了エラー
    Write-Host '完了'                                     # 実行されてしまう
}
catch {
    Write-Host 'ここには来ない'
}

# 【定石】-ErrorAction Stop で終了エラーに昇格させ、catchで扱えるようにする
try {
    Get-Content -Path 'C:\Data\存在しないファイル.txt' -ErrorAction Stop
    Write-Host '完了'                                     # エラー時は実行されない
}
catch {
    Write-Host "捕まえた: $($_.Exception.Message)"
}

-ErrorAction Stop は、そのコマンドの非終了エラーを終了エラーへ昇格させます。内部では ActionPreferenceStopException を使って伝播させ、try内の失敗をcatchで扱えるようにします。catchで読むレコードと例外のプロパティは、3章で分けて確認します。1

最初から終了エラーになる失敗もある

次のような失敗はステートメント終了エラーであり、try内ならStopの追加なしでもcatchに入ります。1

発生源 例
存在しないコマンドの呼び出し CommandNotFoundException
パラメーターの束縛失敗 数値パラメーターへ変換できない文字列を渡すなどの ParameterBindingException
.NETメソッドが投げた例外 [int]::Parse('abc')
コマンドレット・高度な関数が継続不能を報告 $PSCmdlet.ThrowTerminatingError()

次の前半は、既定の設定でtryの外に置いた例です。後半では同じ失敗をtry/catchで捕捉しています。

# ステートメント終了エラー: この文は止まるが、次の文は実行される
[int]::Parse('abc')
Write-Output 'この行は実行される'

# 終了エラーなので、-ErrorAction Stop を付けなくてもcatchに入る
try   { [int]::Parse('abc') }
catch { Write-Warning "捕まえた: $($_.Exception.Message)" }

「ファイルが無い」という同じ状況でも、呼ぶコマンドとその実装によってエラーの種類は変わります。実務では、非終了エラーも捕まえたいコマンドレットの行へStopを明示することで、種類の見落としを減らします。外部EXEにはこの方法をそのまま当てはめず、4章の終了コード判定を使います。

スクリプト全体の既定値と、1コマンドの指定

無人実行では、冒頭で $ErrorActionPreference = 'Stop' とし、「失敗を握りつぶして進むより、止まって報告する」を既定にするのが安全です。一方、対話的な調査では、エラーがあっても残りの対象を調べたい場合があります。用途で使い分けてください。4

-ErrorAction は1コマンドの設定として優先されます。ただし、-ErrorAction と $ErrorActionPreference は完全に対称ではありません。前者が直接制御するのは非終了エラーで、ThrowTerminatingError() のような終了エラーを一律に抑える指定ではありません。1

設定の影響範囲も確認します。$ErrorActionPreference はそのスコープと子スコープへ影響しますが、モジュールは独自のセッション状態を持つため、「呼び出したすべてのモジュールへ必ず同じ設定が伝わる」とは考えないでください。失敗を許容する後始末だけに -ErrorAction SilentlyContinue を指定するなど、許容範囲を狭くします。45

3. catchの中で何を読むか ── ErrorRecordの歩き方

原因・位置・対象をセットで残す

catch内の $_ はErrorRecordです。メッセージだけでなく、「どの処理が、どの対象で失敗したか」まで取り出してログへ残します。16

try {
    Copy-Item -Path $src -Destination $dest -ErrorAction Stop
}
catch [System.IO.IOException] {
    # 型を指定したcatchで「想定内の失敗」だけを個別に処理する。
    # 昇格されたエラーでも、エンジンが元の例外型でマッチングしてくれる
    Write-Warning "I/Oエラー: $($_.Exception.Message)"
}
catch {
    # 想定外はコンテキストごとログに残して再スロー(握りつぶさない)
    $rec = $_   # $_はErrorRecord
    Write-Warning ('種別: {0} / 位置: {1} / 対象: {2}' -f `
        $rec.Exception.GetType().FullName,
        $rec.InvocationInfo.PositionMessage,
        $rec.TargetObject)
    throw       # 引数なしthrowで同じエラーを上位へ伝播
}
finally {
    # finallyは成功時もエラー時も、Ctrl+Cで止められても実行される。後始末はここへ
    if ($tempFile -and (Test-Path $tempFile)) { Remove-Item $tempFile -ErrorAction SilentlyContinue }
}
読む情報 主な用途
$_.Exception 例外本体。型やメッセージを調べる
$_.Exception.GetType().FullName どの種類の例外として届いたかを記録する
$_.InvocationInfo.PositionMessage スクリプトのどの行・コマンドで失敗したかを調べる
$_.TargetObject 失敗した処理の対象を確認する

通常の入口は $_ 自体です。内部で昇格用の例外が使われても、catchには元のErrorRecordが渡り、$_.Exception が元の例外になる場合があります。$_.Exception.ErrorRecord は、その例外が該当プロパティを持つ場合の経路であり、どのエラーにも必ず存在する共通の入口ではありません。62

型指定catchは、継承関係や内部の例外を考慮してマッチします。そのため、上のように想定する例外型を書けます。ただし、型が想定内であることと、復旧できたことは別です。上の IOException のcatchは分岐を示す例で、警告を書くだけで正常扱いにしてよいのは、その失敗を本当に許容できる場合だけです。復旧できなければ、想定外のcatchと同様に throw で上位へ伝えます。2

後始末はfinally、最終報告は境界へ

finallyは成功時・エラー時に加え、Ctrl+Cによる停止時やcatch内のexitでも実行されます。接続のクローズや一時ファイルの削除はここへ置きます。ただし、プロセスの強制終了や電源断まで後始末の実行を保証する仕組みではありません。2

ログを書く場所も揃えます。「例外処理でcatchとログはどこに置くべきか」で扱った境界でcatchする、握りつぶさない、二重ログを避けるという原則は、PowerShellでも同じです。下位の処理は必要な復旧や再試行を担当し、最終的な成否は無人実行の入口でまとめます。

4. ネイティブコマンドの成否 ── $?と$LASTEXITCODEと7.4の新機能

「外部EXEが非ゼロで終わった」は別経路

robocopy、git、社内のEXEなどは、PowerShellのエラーではなくそのプログラムの終了コードで結果を返します。次は「プログラムを起動でき、非ゼロ終了コードで終わった場合」の既定の挙動です。EXEが見つからないなどの起動自体のエラーまで、catchできないという意味ではありません。1

事象 挙動(既定)
非ゼロ終了コード $? が $false になり、$LASTEXITCODE に終了コードが入る
ErrorRecordの生成 されない($Error にも追加されない)
try/catch 入らない

$? は直前の操作の成功・失敗を示すブール値です。$LASTEXITCODE は最後の外部プログラム、またはexitしたスクリプトの終了コードを保持し、コマンドレットのエラーでは変わりません。外部EXEの直後に判定し、後で使うなら別の変数へ保存してください。7

PowerShell 7では、外部EXEの終了コードが0のときだけ $? が $true になります。Windows PowerShell 5.1には、stderrへ書いただけで $? が $false になる場合もありました。stderrの有無だけで成否を決めず、そのコマンドが定義する終了コードを読むのが基本です。8

# ネイティブコマンドは$LASTEXITCODEで判定する
robocopy.exe 'D:\Reports' '\\fileserver\reports' /MIR /R:2 /W:5
if ($LASTEXITCODE -ge 8) {
    # robocopyは0〜7が成功系(コピーの有無等の情報)、8以上が失敗
    throw "robocopyが失敗しました (ExitCode=$LASTEXITCODE)"
}

robocopyは0〜7をコピーの有無などを表す成功系の情報として使い、8以上がコピー失敗を含む値です。非ゼロだから一律に失敗とはできません。なお、例の /MIR は宛先だけにあるファイルの削除も伴うため、試すときは使い捨てのコピー元・宛先で確認してください。9

PowerShell 7.4以降は、非ゼロをエラーへ変換できる

$PSNativeCommandUseErrorActionPreference = $true にすると、非ゼロ終了コードから非終了エラーが発生し、$ErrorActionPreference に従います。Stopと組み合わせればtry/catchで扱えます。既定は $falseです。14

実行環境 設定の扱い
Windows PowerShell 5.1 変数自体がない。代入しても通常の変数を作るだけで、この機能は働かない
PowerShell 7.3 実験的機能 PSNativeCommandErrorActionPreference。有効化とセッション再起動が必要だった
PowerShell 7.4以降 正式機能。$true とStopの組み合わせで非ゼロ終了コードをcatchできる

この違いから、5.1と7が混在するなら $LASTEXITCODE 判定に統一するのが安全です。7.4以降だけで使う場合も、robocopyのような「非ゼロでも失敗ではない」コマンドを分けます。104

# PowerShell 7.4以降: 外部コマンドの失敗もtry/catchで扱う(既定は$false)
$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'

try {
    git.exe fetch origin
}
catch {
    Write-Warning "gitが失敗: $($_.Exception.Message)"
    throw
}

& {
    # robocopyのように非ゼロ=失敗ではないコマンドは、スクリプトブロック内で
    # 一時的に無効化して従来どおり$LASTEXITCODEで判定する(抜ければ元に戻る)
    $PSNativeCommandUseErrorActionPreference = $false
    robocopy.exe 'D:\Reports' '\\fileserver\reports' /MIR
    if ($LASTEXITCODE -ge 8) { throw "robocopy失敗 (ExitCode=$LASTEXITCODE)" }
}

後半の & { ... } は別のスコープで設定を一時的に $false にし、抜けたら元の設定へ戻す例です。前半のgitが失敗して再スローされた場合は、後半へは進みません。2つの扱いを並べて示しています。4

5.1と7の差を含む移行の注意点は、「Windows PowerShell 5.1とPowerShell 7の違いと移行」も参照してください。

5. exit code設計 ── タスクスケジューラと監視から成否を判定できるようにする

ログに残すだけでなく、プロセスの終了コードへ変換する

catchに入って警告を出した後、そのまま正常完了すれば、外部からは成功に見えることがあります。タスクスケジューラや監視が機械的に使う成否の入口として、プロセスの終了コードを設計します。

exit <数値> は終了コードを明示し、$LASTEXITCODE にも値を設定します。ただし、外側のPowerShellをどう起動するかで、その値の渡り方が違います。117

起動方法 終了コードの扱い
pwsh -File / powershell.exe -File スクリプトの exit の値を返す。exitがなく正常完了すれば0、未処理例外で終了すれば1
-Command からスクリプトを呼ぶだけ そのままではスクリプトの10などの値を保持せず、最後のコマンドの成否から0/1になる
-Command 内で明示的に転送する 呼び出しの後に exit $LASTEXITCODE を書けば保持できる。コマンド文字列に直接 exit 10 と書く場合も10を返す

独自の終了コードを運用に使うなら、余計なラッパーを挟まず-File で起動するのが定石です。-Command は一切使えないのではなく、値を保持するには外側での明示的な転送が必要、という違いです。3

無人実行の骨格と、業務側で決めること

次は、業務処理を Export-DailyData へ分け、終了コードと実行証跡を返す骨格です。業務関数、ログの保存先・書き込み権限、エラー分類は、運用に合わせて用意してください。

# Invoke-NightlyExport.ps1 ── タスクスケジューラから成否を判定できる骨格
[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'   # 無人実行では「止まって報告」を既定にする

# 標準出力・エラーを含む実行証跡をログとして残す(-Appendで日次ファイルに追記)
Start-Transcript -Path "C:\Logs\NightlyExport_$(Get-Date -Format yyyyMMdd).log" -Append

try {
    Export-DailyData      # 業務処理本体(モジュール化した関数を呼ぶ)
    exit 0                # 成功を明示
}
catch [System.Net.WebException] {
    Write-Warning "通信エラー: $($_.Exception.Message)"
    exit 10               # 一時的エラー系 ── タスク側で再実行を構成する余地を残す
}
catch {
    Write-Warning "想定外のエラー: $($_.Exception.Message)"
    Write-Warning $_.InvocationInfo.PositionMessage
    exit 1                # 恒久的エラー ── 再実行せず人が見る
}
finally {
    Stop-Transcript       # finallyならexit経由でも証跡が閉じられる
}

ここでの0・1・10は、このスクリプトが決めた運用上の意味です。

値の例 運用上の意味
0 正常完了
1 自動再試行せず、人が調べる失敗
10番台 再試行可能と判定した一時的エラー

WebException なら何でも一時的、とは判断しないでください。この骨格は通信エラーの分岐例であり、実際には応答コードや原因を見て、再試行可能な失敗だけを10番台へ対応づけます。PowerShell 7のWebコマンドでは例外型も異なるため、次章の仕分けと合わせて調整します。12

また、10という値を書けばタスクスケジューラが自動的に「10だけ再実行、1は再実行しない」と解釈するわけではありません。終了コードの意味と、ジョブ管理側の再実行条件を対応づける必要があります。失敗時の再実行を設定するときは、スクリプト内のリトライとタスク全体の再実行が重なることも含めて確認します。タスク側の設定は「タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計」を参照してください。

トランスクリプトは、独自ログに重ねる証跡

Start-Transcript はセッションのコマンドやコンソール出力をテキストへ記録します。-Append で日次ファイルへ追記でき、Stop-Transcript で閉じます。独自のログ関数が拾っていない表示も残すための保険として、併用する価値があります。13

ただし、内部のすべての値や出力が必ず記録されるわけではありません。オブジェクトの表示整形が Stop-Transcript の後に行われ、画面に出ても記録には残らない場合があります。成否の判定は終了コード、業務上必要な証跡は明示的なログでも残します。14

上の骨格では Start-Transcript をtryの前で実行しています。ログを開始できなければ業務処理へ進まず、未処理エラーとして終了します。ログの開始・終了の失敗まで独自コードに分類したい場合は、その処理も設計対象です。保存先の権限や肥大化対策は「PowerShellスクリプト応用 ── ログ調査・アーカイブ・レポート化を安全に自動化する」で扱っています。

6. リトライ設計 ── 一時的エラーと業務エラーを区別する

再実行する前に、3つの条件を揃える

リトライの目的は、時間が解決しうる失敗を自動で吸収することです。恒久的な失敗を繰り返したり、二重処理でデータを壊したりしては逆効果です。

条件 確認すること
一時的エラーに限る ネットワーク瞬断、一時ロック、依存サービスの起動待ちなどか。入力不正・権限不足・設定ミスなら即時失敗させる
回数と間隔に上限を置く 試行回数を決め、2秒・4秒・8秒のように間隔を広げる。障害中の相手へ要求を集中させない
処理を冪等にする 一時ファイル+リネームで結果を公開する、処理済みIDで二重取り込みを弾くなど、同じ処理がもう一度走っても壊れないようにする

「もう一度実行しても安全」は、リトライ関数ではなく業務処理側の条件です。スクリプト内の局所的な再試行にも、タスク全体の再実行にも必要になります。

共通関数は、対象の型・回数・間隔を受け取る

次の関数は、対象の例外型だけを再試行し、最後まで失敗したら元のエラーを上位へ返します。渡す処理は -ErrorAction Stop などで失敗をcatchへ届けることが前提です。

function Invoke-WithRetry {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [scriptblock] $Operation,
        # 0以下を渡されると一度も実行せず正常終了してしまうため、1以上を強制する
        [ValidateRange(1, 100)]
        [int] $MaxAttempts = 4,
        # 負の値はリトライ時のStart-Sleepで別のエラーになるため、束縛時点で弾く
        [ValidateRange(0, 3600)]
        [int] $BaseDelaySeconds = 2,
        # 再試行する価値のある例外型だけを列挙する(既定は通信・I/O系)
        [Type[]] $RetryableExceptions = @([System.IO.IOException], [System.Net.WebException])
    )
    for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
        try {
            # 出力はいったん変数に受け、成功してから返す。& $Operationを直接returnすると、
            # 途中まで出力した後に例外が起きた場合、部分的な出力が呼び出し元へ流れてしまい、
            # 再試行の成功時に同じデータが二重に届く
            $output = & $Operation
            return $output
        }
        catch {
            $ex = $_.Exception
            $isRetryable = $RetryableExceptions | Where-Object { $ex -is $_ }
            if (-not $isRetryable -or $attempt -eq $MaxAttempts) {
                throw   # 業務エラー、または再試行上限 ── そのまま失敗させる
            }
            # 指数で増える待ち時間に上限を設ける(回数が多い構成でも待ちすぎず、
            # Start-Sleepの受け付け範囲も超えない)
            $delay = [math]::Min($BaseDelaySeconds * [math]::Pow(2, $attempt - 1), 300)
            Write-Warning "失敗(${attempt}回目): $($ex.Message) ── ${delay}秒後に再試行します"
            Start-Sleep -Seconds $delay
        }
    }
}

# 使い方: 対象の処理は -ErrorAction Stop で終了エラー化しておくこと
Invoke-WithRetry -Operation {
    Copy-Item -Path '\\fileserver\out\daily.csv' -Destination 'D:\Work' -ErrorAction Stop
}

# PowerShell 7のInvoke-RestMethod / Invoke-WebRequestを再試行する場合の注意:
# 7では通信失敗が5.1時代のWebExceptionではなくHttpRequestException系で届くため、
# 既定のままでは再試行されない。また404のような恒久的なHTTPエラー応答も同じ型で
# 届くため、応答が返ってきた場合はステータスコードで「再試行する価値」を自分で仕分ける
Invoke-WithRetry -RetryableExceptions ([System.Net.Http.HttpRequestException]) -Operation {
    # -SkipHttpErrorCheckでエラー応答でも例外にせず受け取り、コードを見て投げ分ける
    $r = Invoke-WebRequest -Uri 'https://api.example.co.jp/orders' -TimeoutSec 30 -SkipHttpErrorCheck
    if ($r.StatusCode -in 408, 429, 500, 502, 503, 504) {
        # 一時的なコードだけHttpRequestExceptionとして投げる → 再試行される
        throw [System.Net.Http.HttpRequestException]::new("一時的なHTTPエラー: $($r.StatusCode)")
    }
    if ($r.StatusCode -ge 400) {
        throw "恒久的なHTTPエラー: $($r.StatusCode)"   # 型が違うので再試行されない
    }
    $r.Content | ConvertFrom-Json
}

このコードは、次の順で読むと追いやすくなります。

箇所 役割
ValidateRange 試行回数0以下や負の待ち時間を、処理を始める前に弾く
$MaxAttempts 初回を含む総試行回数。既定4なら、追加の再試行は最大3回
$output = & $Operation 成功出力をいったん受け、処理が成功してから呼び出し元へ返す
$RetryableExceptions catchで得た例外を型で選別し、対象外ならすぐ再スローする
待ち時間の計算 既定では2秒・4秒・8秒。1回の待ち時間は最大300秒で、最後の失敗後は待たない

出力を変数に受けるのは、失敗した試行が途中まで出した値を、次の成功結果と一緒に呼び出し元へ流さないためです。ただし、ファイル更新・DBへの書き込み・API送信などの副作用を取り消す処理ではありません。警告など他のストリームを巻き戻すものでもなく、冪等性の代わりにはなりません。

同様に、300秒は1回の待ち時間の上限であり、処理全体のタイムアウトではありません。対象処理のタイムアウトや、タスク全体に許す時間も別に確認します。

例外の型だけで、一時障害とは断定しない

既定の IOException と WebException は分類の出発点です。これらには、待っても解決しない失敗も含まれます。$RetryableExceptions は実際に観測したエラーに合わせて絞り、必要ならOperation側で原因を仕分けてください。「catchしたから全部リトライ」も「通信系の型だから全部リトライ」も避けます。

この関数の型判定は $_.Exception に対する -is です。型指定catchと同じように内部例外をすべてたどる実装ではありません。.NETメソッドの例外がラップされて届くなど、想定した型と一致しない場合もあります。3章の情報を記録し、使うコマンド・バージョンでテストします。

PowerShell 7のHTTP例は、応答コードを先に仕分ける

後半のHTTP例はPowerShell 7向けです。5.1時代の WebException だけを指定しても、7のWebコマンドで届く HttpRequestException 系の失敗には一致しません。一方、404などの恒久的なHTTPエラー応答まで同じ系統に含まれ得るため、型だけで再試行を決めない形にしています。12

-SkipHttpErrorCheck でHTTPエラー応答を通常の応答として受け取り、408・429・500・502・503・504だけをこの例の再試行対象として投げ直します。それ以外の400以上は別の型として失敗させます。このステータス一覧もAPIの契約や操作の安全性を確認して使う例であり、どのAPIにも無条件で適用する規則ではありません。応答を得られない通信失敗やタイムアウトが、すべて同じ例外型になるとも限りません。12

貼り付けずに、モジュール化してテストする

Invoke-WithRetry は Retry.psm1 のような共通モジュールへ置き、Import-Module で読み込みます。毎回貼り付けると、あるスクリプトだけ古いcatchや上限設定が残るためです。使い方の呼び出し例までモジュール読み込み時に実行しないよう、関数定義と呼び出し側は分けてください。

モジュール化は「PowerShellの引数設計とモジュール化」、検証は「PesterによるPowerShellのテスト整備 ── 運用スクリプトを壊しにくくする実務の型」を参照してください。初回成功、一時失敗後の成功、上限到達、対象外の失敗、部分出力の扱いは、テストに固定しておきたい分岐です。

7. 実務の定石(判断表)

実装を見直すときは、次の6点を順番に確認します。表中の -Command の注意は、5章で説明した「終了コードを明示的に転送せず、スクリプトを呼ぶ場合」の話です。

論点 選択肢 判断の目安
エラーの既定挙動 Continueのまま / 冒頭で$ErrorActionPreference = ‘Stop’ 無人実行は「止まって報告」が安全。対話用の調査スクリプトはContinueのままでよい4
catchしたい箇所 祈る / -ErrorAction Stopを明示 コマンドレットは非終了エラーが多い。捕まえたい行にはStopを明示する1
ネイティブコマンドの成否 放置 / $LASTEXITCODE判定 / 7.4の$PSNativeCommandUseErrorActionPreference 5.1混在環境は$LASTEXITCODE判定に統一。7.4以降のみなら新機能+robocopy等の例外区間104
成否の外部報告 ログのみ / exit codeを設計して-File起動 ログは人間用、exit codeは機械用。両方必要。-Command起動は終了コードが潰れる3
実行証跡 独自ログのみ / Start-Transcript併用 独自ログが拾えない出力(外部コマンドの標準出力など)ごと残せる保険13
リトライ 全エラー対象 / 一時的エラー限定+指数バックオフ+冪等 業務エラーのリトライは事故のもと。上限・間隔・冪等の3点セットで

8. まとめ

  • 失敗を捕まえる経路を揃える。非終了エラーも捕まえたい行には -ErrorAction Stop、無人実行の既定値はStopを検討します。外部EXEは $LASTEXITCODE で判定し、7.4以降の新機能を使う場合もrobocopyなどの例外を分けます。
  • 原因と成否を、別々に伝える。catchではErrorRecordから型・メッセージ・位置を残し、復旧できない失敗は再スローします。後始末はfinallyへ、運用側への報告は -File 起動と明示的なexit codeへまとめます。
  • 再実行の安全性を先に作る。リトライは一時的エラーだけを対象に、回数と待ち時間を制限します。局所的なリトライでもタスク全体の再実行でも、二重処理を防ぐ冪等性が前提です。

関連記事

関連する相談領域

合同会社小村ソフトでは、夜間バッチ・定型処理スクリプトのエラー処理とリトライ設計のレビュー、「失敗しているのに成功扱いになる」「月に1回だけ落ちる」といった間欠障害の調査、既存スクリプト資産の運用品質改善を扱っています。

参考リンク

  1. Microsoft Learn, about_Error_Handling. 非終了エラー・ステートメント終了エラー・スクリプト終了エラーの3分類、非終了エラーが既定でcatch/trapに入らないこと、-ErrorAction Stopによる昇格の仕組み(内部のActionPreferenceStopExceptionと元のErrorRecord)、型指定catchが元の例外型でマッチすること、$?と$LASTEXITCODEの仕様、ネイティブコマンドの非ゼロ終了コードが既定ではErrorRecordを生成しないこと、$PSNativeCommandUseErrorActionPreferenceの動作について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11

  2. Microsoft Learn, about_Try_Catch_Finally. try/catch/finallyの構文、型指定catchブロックと複数catch、finallyブロックが成功時・エラー時に加えCtrl+Cによる停止時やcatch内のexit時にも実行されることについて。 ↩ ↩2 ↩3 ↩4 ↩5

  3. Microsoft Learn, about_Pwsh. -File起動時の終了コードの決まり方、-Command起動では0と1以外の終了コードが1に変換されるため終了コードを保持するにはexit $LASTEXITCODEが必要になることについて。 ↩ ↩2 ↩3

  4. Microsoft Learn, about_Preference_Variables. $ErrorActionPreferenceの既定値がContinueであること、-ErrorActionパラメーターが個々のコマンドで優先されること、設定がスコープと子スコープに適用されること、$PSNativeCommandUseErrorActionPreferenceの既定値が$falseであることと、robocopyのような非ゼロ終了コードを情報として使うコマンドでスクリプトブロック内で一時的に無効化する例について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  5. Microsoft Learn, about_Scopes. スコープと子スコープの設定の関係、モジュールが独自のセッション状態を持つことについて。 ↩

  6. Microsoft Learn, Everything you wanted to know about exceptions. catchブロック内で$_から例外情報にアクセスできること、-ErrorAction Stopを付けたコマンドとWrite-Errorのエラーがcatchで扱えるようになること、try/finallyによるリソース解放パターンについて。 ↩ ↩2

  7. Microsoft Learn, about_Automatic_Variables. $LASTEXITCODEがネイティブプログラムやスクリプトの終了コードを保持すること、pwsh -File呼び出し時に例外終了で1・exitキーワードの値・正常完了で0が設定されることについて。 ↩ ↩2

  8. Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. PowerShell 7ではネイティブコマンドがstderrに書き込んだだけでは$?が$falseにならず、非ゼロ終了コードのときのみ$falseになるよう変更されたことについて。 ↩

  9. Microsoft Learn, Robocopy. 終了コード8以上がコピー失敗を示すこと、/MIRがサブディレクトリの複製と宛先の余分なファイル・ディレクトリの削除を伴うことについて。 ↩

  10. Microsoft Learn, What’s New in PowerShell 7.4. 実験的機能PSNativeCommandErrorActionPreference($PSNativeCommandUseErrorActionPreference)がPowerShell 7.4で正式機能(mainstream)になったことについて。 ↩ ↩2

  11. Microsoft Learn, about_Language_Keywords. exitキーワードが終了コードを設定し$LASTEXITCODEにも反映されること、pwsh -Fileで起動したスクリプトがexitの数値引数を終了コードとして返すこと、exit文がない場合は正常完了で0・未処理例外で1になることについて。 ↩

  12. Microsoft Learn, Invoke-WebRequest. HTTP応答のStatusCode、-SkipHttpErrorCheckでHTTPエラー応答も通常の応答として扱うこと、タイムアウトや再試行パラメーターの適用範囲について。 ↩ ↩2 ↩3

  13. Microsoft Learn, Start-Transcript. セッションのコマンドとコンソール出力をテキストファイルに記録すること、-Appendによる追記、既定の保存先とファイル名、Stop-Transcriptによる停止について。 ↩ ↩2

  14. Microsoft Learn, Limitations of PowerShell transcripts. 表示整形がトランスクリプト停止後に行われるなど、画面の出力と記録内容が一致しない場合があることについて。 ↩

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

PowerShellでtry/catchを書いたのにcatchに入らないのはなぜですか?
コマンドレットが出すエラーの多くが非終了エラー(non-terminating error)だからです。try/catchが捕まえるのは終了エラーだけで、非終了エラーはメッセージを表示して処理を続行し、catchには入りません。対処の定石は、捕まえたいコマンドに -ErrorAction Stop を付ける(またはスクリプト冒頭で$ErrorActionPreference = 'Stop'にする)ことです。これにより非終了エラーが終了エラーに昇格し、try/catchで扱えるようになります。
$?と$LASTEXITCODEはどう使い分けますか?
$?は直前の操作が成功したかを示すブール値で、コマンドレットにもネイティブコマンドにも設定されます。$LASTEXITCODEは最後に実行したネイティブプログラム(またはexitしたスクリプト)の終了コードで、コマンドレットのエラーでは変化しません。robocopyやgitなどの外部コマンドの成否を判定するときは、終了コードの意味まで確認できる$LASTEXITCODEで判定するのが確実です。ネイティブコマンドの非ゼロ終了コードは既定ではcatchに入らない点に注意してください。
PowerShellスクリプトの成否をタスクスケジューラから判定するにはどうしますか?
スクリプトの最後(とcatchブロック)でexitキーワードにより終了コードを明示し、タスク側は pwsh -File(またはpowershell.exe -File)で起動して「前回の実行結果」の値を監視します。-Fileで起動した場合、exitで指定した値がそのままプロセスの終了コードになり、exitがなければ正常終了で0、未処理の例外で1になります。-Commandからスクリプトを呼ぶだけでは0と1以外の終了コードが1へ変換されます。-Command内でexit $LASTEXITCODEにより明示的に転送する方法もありますが、独自の終了コードで運用を組むなら-File起動が定石です。
リトライはどんなエラーに対して行うべきですか?
再試行すれば結果が変わりうる一時的エラー(ネットワークの瞬断、ファイルの一時的なロック、サービスの起動待ちなど)に限定します。入力データ不正や権限不足、設定ミスのような業務エラー・恒久的エラーは、何度やっても失敗するのでリトライせず即時に失敗させ、ログとexit codeで人に知らせます。リトライする場合も回数と間隔に上限を設け、指数バックオフで間隔を広げること、そして再実行しても二重処理にならないよう処理を冪等に設計しておくことが前提です。
PowerShell 7.4の$PSNativeCommandUseErrorActionPreferenceは何をする設定ですか?
ネイティブコマンドが非ゼロの終了コードで終わったときに、PowerShellのエラー(非終了エラー)を発生させる設定です。PowerShell 7.3で実験的機能として追加され、7.4で正式機能になりました(既定は$false)。$trueにすると$ErrorActionPreferenceに従うため、Stopと組み合わせれば外部コマンドの失敗をtry/catchで捕まえられます。ただしrobocopyのように非ゼロ終了コードを正常な情報として使うコマンドもあるため、その区間だけ$falseに戻すなどの配慮が必要です。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る