「スクリプトを実行して、終わるまでコーヒーを飲んで待っていた。戻ってみたら、エラーで全件処理が失敗していた……」
PowerShellを使った自動化を行っている人なら、一度はこうした絶望を味わったことがあるのではないでしょうか。多くのエンジニアが抱える悩みは、「コマンドが失敗したのに、スクリプトがそれを無視して突き進んでしまう」という現象です。
実は、PowerShellには実行直後の状態を一瞬で見抜く「黒魔術」のような変数が存在します。それが $?(ドル・クエスチョン)です。この一文字の記号に宇宙を込めることで、あなたのスクリプトは「祈りながら実行するもの」から「確信を持って運用するもの」へと劇的に進化します。
今回は、システム運用やデバッグの現場で必須となる $? の使い方と、堅牢なスクリプトを構築するための実践的なテクニックを徹底解説します。
なぜあなたのスクリプトは止まらないのか? $? の基本
「コマンドが失敗したなら、普通はそこで止まるべきではないか?」と思うかもしれません。しかし、PowerShellはデフォルトの設定では、致命的でないエラー(Non-terminating error)が発生しても処理を中断せず、次の行へ進んでしまう仕様になっています。
これは、パイプライン処理において一部のデータがエラーになっても、全体の処理を止めたくないという利便性を優先しているためです。しかし、この「親切心」が仇となり、前の工程が失敗しているのに次の削除コマンドが動いてしまうような、瓦解の連鎖を招くリスクを孕んでいます。
$? が返す True と False の正体
そこで登場するのが、状態変数 $? です。これは「直前に実行したコマンドが成功したかどうか」を保持する専用の変数です。
使い方は非常にシンプルです。
- 直前のコマンドが成功した場合:
$true - 直前のコマンドが失敗した場合:
$false
たとえば、存在しないディレクトリに移動しようとした直後に $? を呼び出すと、そこには False という「沈黙の悲鳴」が格納されています。逆に、正常に終了した直後であれば True が返ります。
あるベテランエンジニアはこう言いました。「$? は、次のステップに進むための地雷探知機だ。音が鳴っている(False)のに進む者はいない」と。まさにその通りです。一歩踏み出す前に地面が崩れていないかを確認する。このわずかな手間が、スクリプト全体の信頼性を支える守護神となります。
実践!直前のコマンド成功/失敗で処理を分ける書き方
「$? の中身がわかったところで、どう実務に活かせばいいのか?」という問いに答えていきましょう。最も効果的なのは、エラーが発生した瞬間に処理を打ち切る「ガード節」としての利用です。
if 文を使ったガード節のテンプレート
もっとも汎用的で強力なテンプレートは、以下の形式です。
# 何らかの重要な処理
Copy-Item -Path "C:\Source\Data.txt" -Destination "D:\Backup\"
# 直前の処理が失敗したかチェック
if (-not $?) {
Write-Error "ファイルのコピーに失敗しました。処理を中断します。"
return # または exit
}
# 成功した場合のみ実行される後続処理
Write-Host "コピーが成功しました。次の工程に移ります。"
このように、コマンドの直後に -not $?(もし成功でなければ)という判定を入れるだけで、スクリプトに「思考する能力」が宿ります。
「下味(前のコマンド)がついているかを確認せずに火を通すのは、料理ではなく賭けである」と言われるように、状態を確認せずに次のコマンドへバトンを渡すのは非常に危険です。SNSでは「PowerShellのスクリプトで、前段のチェックを怠ったために空の変数が渡され、意図しないファイルが大量削除された」という悲痛な叫びが定期的に話題になります。
このような悲劇を防ぐために、重要な処理の直後には必ずこの「チェックゲート」を設置する癖をつけましょう。
中級者への道:$? と $LASTEXITCODE の決定的な違い
ここまではPowerShell標準のコマンド(コマンドレット)を前提にしてきましたが、実務では git や ipconfig、あるいは独自に作成した .exe ファイルなどの外部プログラムを呼び出すことも多いでしょう。ここで一つ、重要な落とし穴があります。
外部プログラム(.exe等)を扱う際の注意点
実は、$? は外部プログラムの成否を判定する際、少し「大雑把」になる傾向があります。多くの外部プログラムは、終了時に「終了コード(ExitCode)」を返しますが、PowerShellの $? は「0以外ならFalse、0ならTrue」という非常に単純な翻訳しか行いません。
そこで、外部プログラムの挙動を完璧に掌握したい中級者には、$LASTEXITCODE との併用を推奨します。
$?: 論理値(成功か、失敗か)を返す。直感的だが情報量が少ない。$LASTEXITCODE: 数値(0, 1, 2…)を返す。エラーの詳細な理由(例:ファイルが見つからない=2、アクセス拒否=5など)まで追跡できる。
業界では「外部ツール連携がメインのスクリプトなら $LASTEXITCODE を、PowerShellネイティブな操作なら $? を主軸に据えるのが定石」という見方が広がっています。
例えば、前の走者がバトンを落とした(失敗した)のに、次の走者が走り始めても記録は残りません。同様に、プログラムがどのような理由でバトンを落としたのかを $LASTEXITCODE で正確に把握し、その結果を $? という「言葉」に変えてスクリプト全体の流れを制御することが、プロフェッショナルな実装への第一歩です。
時短効果バツグン!デバッグ時間を最小化する実装レシピ
「失敗を恐れてチェックしすぎるのは非効率だ。開発のスピードが落ちる」という意見もあるかもしれません。あえてエラーを無視して突き進む「楽観的バッチ処理」の方が、記述量が減って楽に感じることもあるでしょう。
しかし、それは目隠しをして高速道路を走るようなものです。事故が起きたとき、どこで何が起きたのかを探す「デバッグ時間」は、事前のチェックを入れる時間の数倍から数十倍に膨れ上がります。
ログ出力と組み合わせた「止まるスクリプト」の美学
確信を持って運用できるスクリプトには「止まる美学」が必要です。以下のレシピは、成功・失敗をログに記録しつつ、デバッグを容易にする構成例です。
$logPath = "C:\Logs\Runtime.log"
function Write-Log {
param($Message)
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') : $Message" | Out-File -FilePath $logPath -Append
}
# 処理実行
New-Item -Path "C:\Conf\App.conf" -ItemType File
# 成功・失敗の記録
if ($?) {
Write-Log "SUCCESS: 設定ファイルの作成に成功しました。"
} else {
Write-Log "ERROR: 設定ファイルの作成に失敗しました。権限を確認してください。"
throw "Fatal Error during configuration."
}
「専門家の間では、スクリプトの品質は『エラーが起きたときの挙動』で決まるという意見が一般的です」。このようにログと状態判定を組み合わせることで、問題が発生した際に「何が原因で(Why)」「どの段階で(Where)」失敗したのかが即座に判明します。
フィードバックの無視は破滅への近道です。一文字の変数 $? を通じてシステムとの対話を繰り返すことで、あなたのスクリプトは「属人化した手順書」から、誰が実行しても同じ安全性が担保される「堅牢な資産」へと変わるのです。
まとめ
PowerShellの $? は、初見では記号にしか見えない「黒魔術」のような存在ですが、その実体はスクリプトの安全を司る極めて論理的な仕組みです。
今回の要点を振り返ります。
$?は直前の成否を$true/$falseで教えてくれる生存戦略の鍵。if (-not $?)を使って、失敗時に処理を止める「ガード節」を設ける。- 外部プログラムの判定には数値で詳細がわかる
$LASTEXITCODEを併用する。
今日からできる最小のアクションとして、まずは自分のスクリプトの中で「絶対に失敗してはいけないコマンド」の直後に、Write-Host "成功した?: $?" と一行書き加えてみてください。それだけで、コマンドが発する沈黙の悲鳴を可視化できるようになります。
長期的には、この成否判定をベースに try-catch や詳細なログ出力を組み合わせた、完璧なエラー制御体系を構築することを目指しましょう。最初は小さな一歩ですが、その積み重ねが「年間120時間=丸5日分の休暇」に相当するデバッグ時間の削減をもたらします。
祈る前に、ドルクエスチョン($?)に問え。
一文字の記号に信頼を込めることが、真のエンジニアへの成長を約束してくれるはずです。
コメント