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

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

「夜間バッチが失敗していたのに、タスクスケジューラ上は成功(0x0)になっていて誰も気づかなかった」「try/catchを書いたのにcatchに入らない」「ネットワークの瞬断で月に1回だけ落ちる」── PowerShellスクリプトを運用に載せると、この種の相談が必ずやってきます。手元で動かして満足するスクリプトと、無人で毎晩動き続けるスクリプトの間には、エラー処理と再実行(リトライ)の設計という壁があります。

やっかいなのは、PowerShellのエラーモデルが一般的なプログラミング言語の例外モデルと微妙に違うことです。「エラーが出ているのに処理が続行する」「catchしたはずなのにすり抜ける」は、バグではなくPowerShellの仕様どおりの動きであることがほとんどで、仕組みを知らないまま書くと「失敗を握りつぶして正常終了するスクリプト」が量産されます。

この記事では、社内の定型処理をPowerShellで自動化している情シス担当者・開発者を対象に、終了エラーと非終了エラーの区別から、ネイティブコマンドの成否判定、タスクスケジューラや監視から成否を判定できるexit code設計、そして一時的エラーに耐えるリトライの型までを、公式ドキュメントの裏付けつきで整理します。

1. まず結論

  • PowerShellのエラーは「非終了エラー」と「終了エラー(ステートメント終了/スクリプト終了)」に分かれます。非終了エラーはメッセージを表示してパイプラインを続行し、既定ではtry/catchに入りません1
  • 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
  • リトライは一時的エラー限定・上限つき・冪等が三原則です。業務エラーをリトライで誤魔化さない、指数バックオフで間隔を広げる、再実行しても二重処理にならない設計にする。この3つが揃って初めて「再実行してよいスクリプト」になります。

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

PowerShellのエラーは3カテゴリーに分かれます。非終了エラー(パイプラインを止めずに報告だけする)、ステートメント終了エラー(その文だけ止めて次の文へ進む)、スクリプト終了エラー(呼び出しスタック全体を巻き戻す)です。1

現場で罠になるのは非終了エラーです。Get-ContentGet-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 一方、.NETメソッドの例外([int]::Parse('abc') など)やコマンド名の解決失敗は最初から終了エラーなので、何もしなくてもcatchに入ります。1

「では常に $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

# 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 にも値を設定します。59
  • pwsh -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 に足していく育て方が現実的です。こうした共通関数はモジュールに切り出しておくと再利用が効きます(「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 の値がそのまま終了コードになり、タスクスケジューラや監視から成否を判定できます。
  • リトライは一時的エラー限定・上限つき指数バックオフ・冪等の三原則で。恒久的エラーは即時失敗させて人に渡します。

関連記事

関連する相談領域

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

参考リンク

  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

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

  3. 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

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

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

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

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

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

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

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

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

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

よくある質問

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

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に戻すなどの配慮が必要です。

著者プロフィール

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

小村 豪

合同会社小村ソフト 代表

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

ブログ一覧に戻る