更新履歴(4件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 分類の軸を1行で整理しました(公式の3カテゴリーは止まる範囲の軸で、この記事は`try`/`catch`に入るかの2分類を主軸に使う、という関係です)。「冪等」の初出に説明を加え、ステートメント終了エラーの具体例と最小コードを追加し、`$PSNativeCommandUseErrorActionPreference`の実験的機能名と5.1では変数自体が存在しないことを本文に格上げしました。
- エラーの分類の説明が不正確でした。まず非終了エラーと終了エラーの2種類に分かれ、終了エラーがさらに分かれる、という関係に直しました。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21590129)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「PowerShellのエラー処理と再実行設計 ── try/catchが効かない罠からexit code・リトライの定石まで」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21590129 https://staging.comcomponent.com/blog/powershell-error-handling-retry-design/
- DOI(最新版)
- 10.5281/zenodo.21590129
- DOI(この版)
- 10.5281/zenodo.21733077
「夜間バッチが失敗していたのに、タスクスケジューラ上は成功(0x0)になっていて誰も気づかなかった」「try/catchを書いたのにcatchに入らない」「ネットワークの瞬断で月に1回だけ落ちる」── PowerShellスクリプトを運用に載せると、この種の相談が必ずやってきます。手元で動かして満足するスクリプトと、無人で毎晩動き続けるスクリプトの間には、エラー処理と再実行(リトライ)の設計という壁があります。
やっかいなのは、PowerShellのエラーモデルが一般的なプログラミング言語の例外モデルと微妙に違うことです。「エラーが出ているのに処理が続行する」「catchしたはずなのにすり抜ける」は、バグではなくPowerShellの仕様どおりの動きであることがほとんどで、仕組みを知らないまま書くと「失敗を握りつぶして正常終了するスクリプト」が量産されます。
この記事では、社内の定型処理をPowerShellで自動化している情シス担当者・開発者を対象に、終了エラーと非終了エラーの区別から、ネイティブコマンドの成否判定、タスクスケジューラや監視から成否を判定できるexit code設計、そして一時的エラーに耐えるリトライの型までを、公式ドキュメントの裏付けつきで整理します。
1. まず結論
- PowerShellのエラーは「非終了エラー」と「終了エラー(ステートメント終了/スクリプト終了)」に分かれます。非終了エラーはメッセージを表示してパイプラインを続行し、既定ではtry/catchに入りません。1
- 数え方が2通りあるので先に整理します。公式ドキュメントの分類は「非終了/ステートメント終了/スクリプト終了」の3カテゴリーで、これはエンジンが「どこまで止めるか」(パイプラインだけ/その文だけ/呼び出しスタック全体)の軸です。1 一方、書き手が最初に知りたいのは「try/catchに入るか」で、この軸では非終了エラーと終了エラーの2種類になります。この記事は2種類の軸を主に使い、必要なところで3カテゴリーの内訳に触れます。
- try/catchで捕まえたいコマンドには
-ErrorAction Stopを付けるのが定石です。Stopは非終了エラーを終了エラーに昇格させ、catchで扱えるようにします。$ErrorActionPreference(既定はContinue)をスクリプト冒頭でStopにする方法もあります。12 -ErrorActionはそのコマンド1つに対して$ErrorActionPreferenceを上書きします。ただし両者は完全に対称ではなく、-ErrorActionが制御できるのは非終了エラーだけです。1- ネイティブコマンド(robocopy、git、外部EXE)の失敗は、既定ではPowerShellのエラーになりません。非ゼロ終了コードは
$?を$falseにし$LASTEXITCODEに入りますが、ErrorRecordは作られずcatchにも入りません。成否は$LASTEXITCODEで判定します。1 - PowerShell 7.4では
$PSNativeCommandUseErrorActionPreferenceが正式機能になりました。$trueにすると非ゼロ終了コードが非終了エラーを発生させ、$ErrorActionPreference = 'Stop'と組み合わせればtry/catchで捕まえられます(既定は$false)。32 - catchの中では
$_がErrorRecordです。$_.Exceptionで例外本体、昇格されたエラーなら$_.Exception.ErrorRecordで元のエラー情報にたどれます。例外の型を指定したcatchブロックで、想定内のエラーだけを個別に処理できます。14 - 成否は必ずexit codeで外部に伝えます。
exitキーワードで終了コードを設定し、pwsh -File/powershell.exe -Fileで起動すればその値がプロセスの終了コードになります。exitがなければ正常終了0・未処理例外1です。56 - リトライは一時的エラー限定・上限つき・冪等(べきとう。同じ処理を何度実行しても結果が変わらない性質。6章で詳述)が三原則です。業務エラーをリトライで誤魔化さない、指数バックオフで間隔を広げる、再実行しても二重処理にならない設計にする。この3つが揃って初めて「再実行してよいスクリプト」になります。
この記事の知識マップ
PowerShellのエラーは、パイプラインを続行する非終了エラーと、その場で処理を止める終了エラーに分かれ、非終了エラーは既定のままではtry/catchで捕まえられません。捕まえたいコマンドにErrorAction Stopを付けて終了エラーへ昇格させることで、失敗を握りつぶしたまま正常終了する事故を防げます。robocopyやgitのようなネイティブコマンドはこの仕組みの外側にあり、成否は終了コード($LASTEXITCODE)で確認するのが基本で、PowerShell 7.4以降は$PSNativeCommandUseErrorActionPreferenceと組み合わせてtry/catchに乗せることもできます。成否は最終的に終了コードとしてタスクスケジューラへ伝え、一時的な障害に限り局所的なretryを、冪等な処理設計と指数バックオフの上限つきで行うことで、無人実行に耐えるスクリプトになります。
flowchart LR
accTitle: PowerShellのエラー処理とリトライ設計の知識マップ
accDescr: 非終了エラーと終了エラーの違い、ErrorAction Stopによる昇格、ネイティブコマンドの成否判定と終了コード、PowerShell 7.4の新機能、局所的なretryが冪等な処理設計・一時的な障害・指数バックオフと持つ関係を示す図
non_terminating_error["非終了エラー"]
limited_retry["局所的なretry"]
powershell["PowerShell"]
powershell_try_catch["PowerShellのtry/catch"]
terminating_error["終了エラー(ステートメント終了/スクリプト終了)"]
erroraction_stop["ErrorAction Stop"]
silent_failure_risk["失敗の握りつぶしリスク"]
native_command["ネイティブコマンド(外部プログラム)"]
exit_code["終了コード(exit code)"]
ps_native_command_use_erroraction_preference["$PSNativeCommandUseErrorActionPreference"]
robocopy["robocopy"]
task_scheduler["タスクスケジューラの無人実行"]
idempotent_processing["idempotency(冪等性)を前提にした処理"]
transient_failure["一時的な障害"]
permanent_error["恒久的エラー(業務エラー)"]
exponential_backoff["指数バックオフ"]
powershell -->|"利用する"| powershell_try_catch
powershell_try_catch -->|"前提とする"| terminating_error
non_terminating_error -.->|"両立しない"| powershell_try_catch
powershell_try_catch -.->|"前提とする"| erroraction_stop
non_terminating_error -.->|"原因になり得る"| silent_failure_risk
erroraction_stop -->|"防止する"| silent_failure_risk
native_command -.->|"両立しない"| powershell_try_catch
native_command -->|"で確認できる"| exit_code
native_command -.->|"原因になり得る"| silent_failure_risk
ps_native_command_use_erroraction_preference -->|"前提とする"| erroraction_stop
ps_native_command_use_erroraction_preference -.->|"防止する"| silent_failure_risk
powershell -->|"利用する"| robocopy
robocopy -->|"で確認できる"| exit_code
exit_code -->|"で確認できる"| task_scheduler
limited_retry -->|"前提とする"| idempotent_processing
limited_retry -->|"軽減する"| transient_failure
limited_retry -->|"用いるのは非推奨"| permanent_error
limited_retry -->|"利用する"| exponential_backoff
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全18件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 2種類のエラー ── なぜtry/catchが効かないのか
PowerShellのエラーは、まず非終了エラーと終了エラーの2種類に分かれます。そして終了エラーはステートメント終了エラーとスクリプト終了エラーに分かれるので、細かく数えると3カテゴリーになります。非終了エラーはパイプラインを止めずに報告だけし、ステートメント終了エラーはその文だけ止めて次の文へ進み、スクリプト終了エラーは呼び出しスタック全体を巻き戻します。1
現場で罠になるのは非終了エラーです。Get-Content や Get-ChildItem のようなコマンドレットが個々の入力の処理に失敗したときに出すのはたいてい非終了エラーで、赤いエラーメッセージは表示されるのに、処理は続行し、try/catchにもtrapにも入りません。1
# 【罠】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 や $ErrorActionPreference = 'Stop' が効いていると、エンジンは非終了エラーを ActionPreferenceStopException で包んで終了エラーに昇格させます。tryブロックの中ではこの昇格したエラーがcatchへ届く、というのが正確なメカニズムです。1
一方、最初から終了エラーになる(=何もしなくてもcatchに入る)ものもあります。ステートメント終了エラーがそれで、公式ドキュメントは次の発生源を挙げています。1
- 存在しないコマンドを呼んだとき(
CommandNotFoundException) - パラメーターの束縛に失敗したとき(
ParameterBindingException。数値のパラメーターに変換できない文字列を渡した、など) - .NETのメソッドが例外を投げたとき(
[int]::Parse('abc')など) - コマンドレットや高度な関数が
$PSCmdlet.ThrowTerminatingError()で「この呼び出しはもう続けられない」と報告したとき
名前のとおり「その文だけ」を止めるので、スクリプトは次の文から実行が続きます。1
# ステートメント終了エラー: この文は止まるが、次の文は実行される
[int]::Parse('abc')
Write-Output 'この行は実行される'
# 終了エラーなので、-ErrorAction Stop を付けなくてもcatchに入る
try { [int]::Parse('abc') }
catch { Write-Warning "捕まえた: $($_.Exception.Message)" }
やっかいなのは、「ファイルが無い」のような同じ状況でも、コマンドレット側の実装によって非終了エラーだったりステートメント終了エラーだったりすることです。書く側が毎回見分けるのは非現実的なので、捕まえたい行には -ErrorAction Stop を明示して、どちらであっても確実にcatchへ届く形に揃えてしまうのが実務の答えになります。
「では常に $ErrorActionPreference = 'Stop' にすればよいのでは」という発想は半分正解です。無人実行のスクリプトでは、エラーを握りつぶして進むより、止まって失敗を報告するほうが安全なので、冒頭でStopにするのは良い既定です。ただし、$ErrorActionPreference はそのスコープと子スコープに効くため、呼び出したモジュールや関数の挙動まで変わること、失敗してもよい後始末処理(一時ファイル削除など)には個別に -ErrorAction SilentlyContinue を付け直す必要があることは意識してください。2
3. catchの中で何を読むか ── ErrorRecordの歩き方
catchブロックの $_ にはErrorRecordが入っています。ログに残すべき情報はここから取れます。14
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 }
}
読みどころは3つです。
$_.Exceptionが例外本体です。-ErrorAction Stopで昇格したエラーはActionPreferenceStopExceptionに包まれますが、catchの型マッチングではエンジンが元の例外型(例:ItemNotFoundException)を見てくれるため、型指定catchはそのまま書けます。元のErrorRecordには$_.Exception.ErrorRecordでたどれます。1$_.InvocationInfo.PositionMessageに「どのファイルの何行目のどのコマンドか」が入っており、無人実行のログではこれがあるかどうかで調査時間が桁で変わります。- finallyブロックは、tryが成功しても、エラーになっても、Ctrl+Cで停止されても実行されます。接続のクローズや一時ファイルの削除といった後始末はfinallyに置きます。7
どの層でcatchしてどこでログを書くかという設計論は、言語をまたいで共通です。「例外処理でcatchとログはどこに置くべきか」で整理した原則(境界でcatchする、握りつぶさない、二重ログを避ける)はPowerShellにもそのまま適用できます。
4. ネイティブコマンドの成否 ── $?と$LASTEXITCODEと7.4の新機能
もう一つの大穴が、robocopyやgit、社内のEXEといったネイティブコマンドです。外部プログラムはPowerShellのエラーシステムに参加せず、失敗を終了コードで報告します。既定の動きはこうです。1
| 事象 | 挙動(既定) |
|---|---|
| 非ゼロ終了コード | $? が $false になり、$LASTEXITCODE に終了コードが入る |
| ErrorRecordの生成 | されない($Error にも追加されない) |
| try/catch | 入らない |
つまり、try { robocopy ... } catch { ... } は(既定では)何も捕まえません。ネイティブコマンドの成否判定は $LASTEXITCODE で書きます。$? は「直前の操作が成功したか」のブール値で、ネイティブコマンドについては終了コード0のときだけ $true になります。1 なお、Windows PowerShell 5.1ではネイティブコマンドがstderrに書き込んだだけで $? が $false になることがありましたが、PowerShell 7では非ゼロ終了コードのときのみ $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)"
}
PowerShell 7.4からは、この扱いを変える $PSNativeCommandUseErrorActionPreference が使えます。7.3で実験的機能として追加され、7.4で正式機能(mainstream)になりました。3 $true にすると、非ゼロ終了コードのネイティブコマンドが終了コードを明示した非終了エラーを発生させ、これが $ErrorActionPreference に従います。つまりStopと組み合わせれば外部コマンドの失敗もtry/catchに乗ります。12
5.1と7が混在する環境では、バージョンの前提を必ず確認してください。この機能が使えるのはPowerShell 7.4以降で、7.3では実験的機能(機能名は PSNativeCommandErrorActionPreference)だったため Enable-ExperimentalFeature での有効化とセッションの再起動が必要でした。3 そしてWindows PowerShell 5.1にはこの変数自体が存在せず、$true を代入しても何も起きません(単に新しい変数が作られるだけで、エラーにもならないので気づきにくい)。同じスクリプトを5.1と7の両方で流す可能性があるなら、この機能には依存せず、次章以降も含めて $LASTEXITCODE による判定で統一するのが安全です。
# 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)" }
}
robocopyの例は公式ドキュメントにもそのまま載っているとおり、非ゼロ終了コードを正常な情報として使うコマンドが存在するため、一括で有効にするなら例外区間の設計が必要です。2 Windows PowerShell 5.1しか使えない現場では、この機能は存在しないので $LASTEXITCODE 判定に統一してください。5.1と7の挙動差は移行時の落とし穴になりやすいので、「Windows PowerShell 5.1とPowerShell 7の違いと移行」もあわせて参照してください。
5. exit code設計 ── タスクスケジューラと監視から成否を判定できるようにする
エラーを捕まえたら、次は外部への報告です。タスクスケジューラや監視ツールがスクリプトの成否を知る手段は、実質的にプロセスの終了コードだけです。仕様を正確に押さえます。
exit <数値>でスクリプトの終了コードを明示できます。exitは$LASTEXITCODEにも値を設定します。59pwsh -File(powershell.exe -File)で起動した場合、exitで指定した値がそのままプロセスの終了コードになります。exit文がなければ、正常完了で0、未処理の例外で終了すると1です。56-Commandでスクリプトを起動すると、スクリプト内のexit 10のような終了コードは保たれません。最後のコマンドの成否から0か1に丸められます(コマンド文字列に直接exit 10と書いた場合はその値が返ります)。スクリプトの終了コードを使い分ける運用なら-File起動が定石です。6
この仕様を骨格に落とすと、無人実行スクリプトのテンプレートはこうなります。
# 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経由でも証跡が閉じられる
}
Start-Transcript はセッションの入力と出力を丸ごとテキストに記録するコマンドレットで、echoやリダイレクトを仕込まなくても「そのとき画面に何が出ていたか」を再現できます。10 独自のログ関数と排他ではなく、最後の砦として併用する価値があります。ログの設計と肥大化対策は「PowerShellスクリプト応用 ── ログ調査・アーカイブ・レポート化を安全に自動化する」で扱っています。
exit codeの割り当ては凝りすぎないのがコツです。0=成功、1=恒久的エラー(人が見る)、10番台=一時的エラー(再実行してよい)程度の粒度で十分で、タスクスケジューラの「前回の実行結果」やジョブ管理ツールの成否判定がそのまま組めます。タスク側の設定(失敗時の再実行、実行結果の確認方法)は「タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計」を参照してください。
6. リトライ設計 ── 一時的エラーと業務エラーを区別する
最後に再実行です。リトライの価値は「一時的エラーを自動で吸収し、夜中に人を起こさないこと」ですが、雑に入れると「恒久的な失敗を延々と再試行する」「二重処理でデータを壊す」という別の事故を生みます。原則は3つです。
- リトライするのは一時的エラーだけ。ネットワーク瞬断、ファイルの一時ロック、依存サービスの起動待ちなど、時間が解決しうる失敗に限定します。入力不正・権限不足・設定ミスは即時失敗させ、exit codeとログで人に渡します。
- 上限と間隔を設計する。回数上限を決め、間隔は指数バックオフ(2秒、4秒、8秒…)で広げます。障害中の相手に一定間隔で叩き続けるのは回復を妨げるだけです。
- 冪等(再実行しても安全)にしておく。リトライもタスクスケジューラの再実行も、「同じ処理がもう一度走る」ことを意味します。出力は一時ファイル+リネームで公開する、処理済みIDを記録して二重取り込みを弾く、といった設計が前提です。
型としてはこの形に集約できます。
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
}
ポイントは、リトライ対象を例外の型で明示的に選んでいることです。「catchしたら全部リトライ」と書くと、パラメーター間違いのような恒久的エラーまで4回試して無駄に待つことになります。運用開始後は、実ログで観測した一時的エラーの型を $RetryableExceptions に足していく育て方が現実的です。
なお、Invoke-WithRetry 本体は長めなので、使うたびにスクリプトへ貼り付けるのではなく、Retry.psm1 のようなファイルに丸ごと保存して Import-Module で読み込む形にしてください。コピペのたびに catch の中だけ古い版が混ざる、といった事故を防げます(モジュール化の作法は「PowerShellの引数設計とモジュール化」参照)。また、リトライやエラー分岐のロジックはまさにPesterでテストを書く価値がある部分です(「PesterによるPowerShellのテスト整備 ── 運用スクリプトを壊しにくくする実務の型」)。
7. 実務の定石(判断表)
| 論点 | 選択肢 | 判断の目安 |
|---|---|---|
| エラーの既定挙動 | Continueのまま / 冒頭で$ErrorActionPreference = ‘Stop’ | 無人実行は「止まって報告」が安全。対話用の調査スクリプトはContinueのままでよい2 |
| catchしたい箇所 | 祈る / -ErrorAction Stopを明示 | コマンドレットは非終了エラーが多い。捕まえたい行にはStopを明示する1 |
| ネイティブコマンドの成否 | 放置 / $LASTEXITCODE判定 / 7.4の$PSNativeCommandUseErrorActionPreference | 5.1混在環境は$LASTEXITCODE判定に統一。7.4以降のみなら新機能+robocopy等の例外区間32 |
| 成否の外部報告 | ログのみ / exit codeを設計して-File起動 | ログは人間用、exit codeは機械用。両方必要。-Command起動は終了コードが潰れる6 |
| 実行証跡 | 独自ログのみ / Start-Transcript併用 | 独自ログが拾えない出力(外部コマンドの標準出力など)ごと残せる保険10 |
| リトライ | 全エラー対象 / 一時的エラー限定+指数バックオフ+冪等 | 業務エラーのリトライは事故のもと。上限・間隔・冪等の3点セットで |
8. まとめ
- PowerShellのエラーは非終了エラーと終了エラーに分かれ、非終了エラーは既定でtry/catchに入りません。捕まえたいコマンドには
-ErrorAction Stopを明示するのが定石です。 $ErrorActionPreferenceの既定はContinueです。無人実行スクリプトは冒頭でStopにし、「失敗を握りつぶして正常終了する」事故を構造的に防ぎます。- ネイティブコマンドの失敗は既定ではcatchに入りません。
$LASTEXITCODEで判定するか、PowerShell 7.4以降なら$PSNativeCommandUseErrorActionPreferenceを活用します。 - catchでは
$_(ErrorRecord)から例外の型・メッセージ・位置情報をログに残し、後始末はfinallyに置きます。finallyはCtrl+Cやexitでも実行されます。 - 成否はexit codeで外部に報告します。
-File起動ならexitの値がそのまま終了コードになり、タスクスケジューラや監視から成否を判定できます。 - リトライは一時的エラー限定・上限つき指数バックオフ・冪等の三原則で。恒久的エラーは即時失敗させて人に渡します。
関連記事
- PowerShellスクリプト応用 ── ログ調査・アーカイブ・レポート化を安全に自動化する
- PesterによるPowerShellのテスト整備 ── 運用スクリプトを壊しにくくする実務の型
- タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計
- 例外処理でcatchとログはどこに置くべきか
- PowerShellの実行ポリシーとスクリプト署名
- PowerShellの引数設計とモジュール化
関連する相談領域
合同会社小村ソフトでは、夜間バッチ・定型処理スクリプトのエラー処理とリトライ設計のレビュー、「失敗しているのに成功扱いになる」「月に1回だけ落ちる」といった間欠障害の調査、既存スクリプト資産の運用品質改善を扱っています。
参考リンク
-
Microsoft Learn, about_Error_Handling. 非終了エラー・ステートメント終了エラー・スクリプト終了エラーの3分類、非終了エラーが既定でcatch/trapに入らないこと、-ErrorAction Stopによる昇格の仕組み(ActionPreferenceStopExceptionと$_.Exception.ErrorRecord)、型指定catchが元の例外型でマッチすること、$?と$LASTEXITCODEの仕様、ネイティブコマンドの非ゼロ終了コードが既定ではErrorRecordを生成しないこと、$PSNativeCommandUseErrorActionPreferenceの動作について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17
-
Microsoft Learn, about_Preference_Variables. $ErrorActionPreferenceの既定値がContinueであること、-ErrorActionパラメーターが個々のコマンドで優先されること、設定がスコープと子スコープに適用されること、$PSNativeCommandUseErrorActionPreferenceの既定値が$falseであることと、robocopyのような非ゼロ終了コードを情報として使うコマンドでスクリプトブロック内で一時的に無効化する例について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, What’s New in PowerShell 7.4. 実験的機能PSNativeCommandErrorActionPreference($PSNativeCommandUseErrorActionPreference)がPowerShell 7.4で正式機能(mainstream)になったことについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Everything you wanted to know about exceptions. catchブロック内で$_から例外情報にアクセスできること、-ErrorAction Stopを付けたコマンドとWrite-Errorのエラーがcatchで扱えるようになること、try/finallyによるリソース解放パターンについて。 ↩ ↩2
-
Microsoft Learn, about_Language_Keywords. exitキーワードが終了コードを設定し$LASTEXITCODEにも反映されること、pwsh -Fileで起動したスクリプトがexitの数値引数を終了コードとして返すこと、exit文がない場合は正常完了で0・未処理例外で1になることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Pwsh. -File起動時の終了コードの決まり方、-Command起動では0と1以外の終了コードが1に変換されるため終了コードを保持するにはexit $LASTEXITCODEが必要になることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Try_Catch_Finally. try/catch/finallyの構文、型指定catchブロックと複数catch、finallyブロックが成功時・エラー時に加えCtrl+Cによる停止時やcatch内のexit時にも実行されることについて。 ↩
-
Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. PowerShell 7ではネイティブコマンドがstderrに書き込んだだけでは$?が$falseにならず、非ゼロ終了コードのときのみ$falseになるよう変更されたことについて。 ↩
-
Microsoft Learn, about_Automatic_Variables. $LASTEXITCODEがネイティブプログラムやスクリプトの終了コードを保持すること、pwsh -File呼び出し時に例外終了で1・exitキーワードの値・正常完了で0が設定されることについて。 ↩
-
Microsoft Learn, Start-Transcript. セッションのコマンドとコンソール出力をテキストファイルに記録すること、-Appendによる追記、既定の保存先とファイル名、Stop-Transcriptによる停止について。 ↩ ↩2
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
PowerShellスクリプトが遅いときに見るところ ── 配列・パイプライン・突合の勘所
PowerShellスクリプトが遅い原因の定番を整理します。配列の+=がO(n^2)になる理由、パイプラインとforeachの差、突合のハッシュテーブル化、ファイルI/Oの改善、そして正しい測り方までを実務目線で解説します。
Write-Hostをやめる ── PowerShellの出力ストリームとログ設計
PowerShellの6つの出力ストリームの使い分け、Write-Hostが抱える問題と正しい使いどころ、関数の戻り値が汚れる原因、-Verboseや-InformationVariableによる呼び出し側制御、構造化ログの残し方を整理します。
PowerShellの並列処理 ── ForEach-Object -Parallelとジョブの使い分け
ForEach-Object -Parallel・Start-ThreadJob・Start-Jobの違いと使い分け、$using:とスレッド安全性、ThrottleLimitの決め方、かえって遅くなるケースまでを実務目線で整理します。
PowerShellから外部exeを正しく呼ぶ ── 引数のクォート・終了コード・文字化けの落とし穴
PowerShellからrobocopyや社内EXEを呼ぶと引数が壊れる、終了コードが取れない、出力が文字化けする。PowerShell 7.3の引数渡し変更、停止解析トークン--%、Start-Processの使い分けまでを実務目線で整理します。
PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する
PowerShellスクリプトの平文パスワードを安全な保管へ移行する手順を整理します。SecureStringの実像と限界、Export-ClixmlによるDPAPI保存の仕組み、SecretManagement/SecretStoreの使いどころまで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 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に変換されてしまうため、終了コードで運用を組むなら-File起動が定石です。
- リトライはどんなエラーに対して行うべきですか?
- 再試行すれば結果が変わりうる一時的エラー(ネットワークの瞬断、ファイルの一時的なロック、サービスの起動待ちなど)に限定します。入力データ不正や権限不足、設定ミスのような業務エラー・恒久的エラーは、何度やっても失敗するのでリトライせず即時に失敗させ、ログとexit codeで人に知らせます。リトライする場合も回数と間隔に上限を設け、指数バックオフで間隔を広げること、そして再実行しても二重処理にならないよう処理を冪等に設計しておくことが前提です。
- PowerShell 7.4の$PSNativeCommandUseErrorActionPreferenceは何をする設定ですか?
- ネイティブコマンドが非ゼロの終了コードで終わったときに、PowerShellのエラー(非終了エラー)を発生させる設定です。PowerShell 7.3で実験的機能として追加され、7.4で正式機能になりました(既定は$false)。$trueにすると$ErrorActionPreferenceに従うため、Stopと組み合わせれば外部コマンドの失敗をtry/catchで捕まえられます。ただしrobocopyのように非ゼロ終了コードを正常な情報として使うコマンドもあるため、その区間だけ$falseに戻すなどの配慮が必要です。