PowerShell 自動変数“$?”と“$LASTEXITCODE”の違いを検証
概要
PowerShellではじめてスクリプトを作成した当時、エラー制御(エラーハンドリング)でハマってしまいました。
よくネット記事のサンプルコードやサンプルプログラムに登場する自動変数は$?ですが、
外部コマンドと連携した際、使い方を間違えてしまい思わぬ挙動になってしまいました。
コマンドプロンプトのエラー制御は%ERRORLEVEL%のみでエラー制御しますが、
PowerShellでは、$?以外に$LASTEXITCODEも合わせて使用する必要がある事がわかりました。
今回、PowerShellの自動変数、$?と$LASTEXITCODEの挙動を直接コマンド(CLI・コマンドレット)と 外部コマンド[外だし自作のスクリプトファイル(ps1ファイル)を使用]の2パターンで検証してみた結果を紹介します。
この記事のターゲット
- PowerShellの初心者の方
- エラー制御がうまくいかず悩んでいる方
- 自動変数の
$?と$LASTEXITCODEの違いを詳しく知りたい方 - コマンドプロンプト(cmd)の
%ERRORLEVEL%とも動作を比較したい方
環境
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
新しいクロスプラットフォームの PowerShell をお試しください https://aka.ms/pscore6
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.19041.3031
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.3031
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
PS C:\WINDOWS\system32>
C:\WINDOWS\system32>ver
Microsoft Windows [Version 10.0.19045.3324]
C:\WINDOWS\system32>
結論
PowerShellで自動変数、$?と$LASTEXITCODEの動作内容と使用用途は下記の通りです。
| 自動変数 | データ型 | 動作内容 | 使用用途 |
|---|---|---|---|
$? |
System.Boolean | 直前のコマンドの結果がTrue or Falseで値がセットされる。 |
直前で実行したコマンド結果を見る時に使用。 外部コマンドと連携する場合は、独特な動きとなりエラー制御が難しいので不向き。 |
$LASTEXITCODE |
System.Int32 | 終了コードが指定された場合(exitコマンド、exit 0 や exit -1などを指定)、指定された値がセットされる。終了コードが指定されていない場合、変更されず初期値のnull( $null)のままとなる。 |
外部コマンドと連携する場合、使いやすい。 ただし、その外部コマンドで終了コードが指定されていなかった場合は使う事ができない。 |
以降より検証した結果を記載します。
最初に混同しやすいコマンドプロンプトでの動きについて検証。
コマンドプロンプトの戻り値(%ERRORLEVEL%)
過去、コマンドプロンプトでバッチファイルを作成していた方は、混乱しやすいと思うので、
まずはコマンドプロンプトの動きを紹介。
コマンドプロンプトでエラー制御する場合は、%ERRORLEVEL%を使い戻り値を取得します。
準備したバッチファイル
D:\Downloads\return_test\bat>dir
ドライブ D のボリューム ラベルは ボリューム です
ボリューム シリアル番号は 1298-06B4 です
D:\Downloads\return_test\bat のディレクトリ
2023/08/21 10:31 <DIR> .
2023/08/21 10:31 <DIR> ..
2023/08/21 11:18 20 ng-pattern.bat
2023/08/21 11:18 33 ng-pattern_set-exitcode404.bat
2023/08/21 09:13 12 ok-pattern.bat
3 個のファイル 65 バイト
2 個のディレクトリ 56,885,624,832 バイトの空き領域
D:\Downloads\return_test\bat>
ECHO CMD-OKAY!
NOT-EXISTS-COMMAND
NOT-EXISTS-COMMAND
EXIT /B 404
戻り値(%ERRORLEVEL%)の検証
以下の検証結果からコマンドプロンプトで実行した場合、直接コマンドで打つ時や外部コマンドを打つ時など、
どのシチュエーションにおいても直前のコマンド結果(終了コードを指定した場合は、指定した値)が%ERRORLEVEL%に反映される。
※一部、%ERRORLEVEL%に値がセットされないコマンドあり(DELコマンドなど)
直接コマンドを実行した場合(%ERRORLEVEL%)
C:\WINDOWS\system32>ECHO CMD-OKAY! // 直接コマンドを実行(存在するコマンド)
CMD-OKAY!
C:\WINDOWS\system32>ECHO %ERRORLEVEL% // 戻り値を確認
0
C:\WINDOWS\system32>NOT-EXISTS-COMMAND // 直接コマンドを実行(存在しないコマンド)
'NOT-EXISTS-COMMAND' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
C:\WINDOWS\system32>
C:\WINDOWS\system32>ECHO %ERRORLEVEL% // 戻り値を確認
9009
C:\WINDOWS\system32>
外部コマンド(自作のバッチファイル)を実行した場合
D:\Downloads\return_test\bat>ok-pattern.bat // バッチの実行
D:\Downloads\return_test\bat>ECHO CMD-OKAY!
CMD-OKAY!
D:\Downloads\return_test\bat>ECHO %ERRORLEVEL% // 戻り値を確認
0
D:\Downloads\return_test\bat>ng-pattern.bat // バッチの実行
D:\Downloads\return_test\bat>NOT-EXISTS-COMMAND
'NOT-EXISTS-COMMAND' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
D:\Downloads\return_test\bat>
D:\Downloads\return_test\bat>ECHO %ERRORLEVEL% // 戻り値を確認
9009
D:\Downloads\return_test\bat>
D:\Downloads\return_test\bat>ng-pattern_set-exitcode404.bat // バッチの実行
D:\Downloads\return_test\bat>NOT-EXISTS-COMMAND
'NOT-EXISTS-COMMAND' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
D:\Downloads\return_test\bat>EXIT /B 404
D:\Downloads\return_test\bat>ECHO %ERRORLEVEL% // 戻り値を確認
404
PowerShellの戻り値($?と$LASTEXITCODE)
PowerShell CLIやプログラム(ps1ファイル)のエラー制御する場合は、$? または $LASTEXITCODEを使って実装します。
準備したスクリプトファイル
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
新しいクロスプラットフォームの PowerShell をお試しください https://aka.ms/pscore6
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> dir
ディレクトリ: D:\Downloads\return_test\ps
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023/08/21 11:18 20 ng-pattern.ps1
-a---- 2023/08/22 9:09 28 ng-pattern_set-exitcode000.ps1
-a---- 2023/08/21 11:18 30 ng-pattern_set-exitcode404.ps1
-a---- 2023/08/21 10:36 23 ok-pattern.ps1
PS C:\WINDOWS\system32>
Write-Host "PS-OKAY!"
Write-Host "PS-OKAY!"
exit 0
Not-Exists-Command
Not-Exists-Command
exit 404
Not-Exists-Command
exit 0
戻り値($?)の検証
以下の検証結果からPowerShellの戻り値(自動変数)$?では、直接コマンドを実行すると直前のコマンド結果が「 True or False 」で反映されることがわかった。
また、外部コマンドを実行した際、終了コードを指定しないと正常値の「 True 」で反映、
終了コードを0で指定すると正常値の「 True 」、0以外で指定すると異常値の「 False」が反映されるもよう。
直接コマンドを実行した場合($?)
PS C:\WINDOWS\system32> Write-Host "PS-OKAY!" // 直接コマンドを実行
PS-OKAY!
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $? // 戻り値を確認
True
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> Not-Exists-Command // 直接コマンドを実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラム
の名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認
してから、再試行してください。
発生場所 行:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $? // 戻り値を確認
False
PS C:\WINDOWS\system32>
外部コマンド(自作のスクリプトファイル)を実行した場合
PS D:\Downloads\return_test\ps> .\ok-pattern.ps1 // スクリプトの実行
PS-OKAY!
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $? // 戻り値を確認
True
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ok-pattern_set-exitcode000.ps1 // スクリプトの実行
PS-OKAY!
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $? // 戻り値を確認
True
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ng-pattern.ps1 // スクリプトの実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラム
の名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認
してから、再試行してください。
発生場所 D:\Downloads\return_test\ps\ng-pattern.ps1:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $? // 戻り値を確認
True
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ng-pattern_set-exitcode404.ps1 // スクリプトの実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラム
の名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認
してから、再試行してください。
発生場所 D:\Downloads\return_test\ps\ng-pattern_set-exitcode404.ps1:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $? // 戻り値を確認
False
PS D:\Downloads\return_test\ps>
ps1ファイルで終了コードを404で指定してみた結果、$?がFalseになりました。
では、終了コードを0で指定してみて実行するとどうなるか下記で確認してみました。
PS D:\Downloads\return_test\ps> .\ng-pattern_set-exitcode000.ps1 // スクリプトの実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラム
の名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認
してから、再試行してください。
発生場所 D:\Downloads\return_test\ps\ng-pattern_set-exitcode000.ps1:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $? // 戻り値を確認
True
PS D:\Downloads\return_test\ps>
上記の通り、終了コードを0で指定した結果、$?がTrueとなりました。
これまでの結果から、PowerShellで外部コマンドを実行した場合、終了コードを指定しないと「 True 」となる。
一方、終了コードを0で指定すると$?は「 True 」となり0以外で指定すると$?は「 False 」となる事がわかりました。
外部コマンド + $?の組み合わせは、かなり独特な動きとなる為、外部コマンドのエラー制御で$?は使用しない方が良いと思われます。
戻り値($LASTEXITCODE)の検証
以下の検証結果からPowerShellの戻り値(自動変数)$LASTEXITCODEでは、直接コマンドを実行した場合は反映されない事がわかった。
また、外部コマンドを実行した際、終了コードを指定しないと結果が反映されないが、
終了コードをで指定すると指定した値がそのまま$LASTEXITCODEに反映される事が判明。
直接コマンドを実行した場合($LASTEXITCODE)
PS C:\WINDOWS\system32> Write-Host "PS-OKAY!" // 直接コマンドを実行
PS-OKAY!
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $LASTEXITCODE // 戻り値を確認
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> Not-Exists-Command // 直接コマンドを実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラム
の名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認
してから、再試行してください。
発生場所 行:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $LASTEXITCODE // 戻り値を確認
PS C:\WINDOWS\system32>
外部コマンド(スクリプトファイル)を実行した場合
PS D:\Downloads\return_test\ps> .\ok-pattern.ps1 // スクリプトの実行
PS-OKAY!
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $LASTEXITCODE // 戻り値を確認
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ok-pattern_set-exitcode000.ps1 // スクリプトの実行
PS-OKAY!
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $LASTEXITCODE // 戻り値を確認
0
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ng-pattern.ps1 // スクリプトの実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラ
ムの名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確
認してから、再試行してください。
発生場所 D:\Downloads\return_test\ps\ng-pattern.ps1:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $LASTEXITCODE // 戻り値を確認
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ng-pattern_set-exitcode404.ps1 // スクリプトの実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラ
ムの名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確
認してから、再試行してください。
発生場所 D:\Downloads\return_test\ps\ng-pattern_set-exitcode404.ps1:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> $LASTEXITCODE // 戻り値を確認
404
PS D:\Downloads\return_test\ps>
PS D:\Downloads\return_test\ps> .\ng-pattern_set-exitcode000.ps1 // スクリプトの実行
Not-Exists-Command : 用語 'Not-Exists-Command' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラ
ムの名前として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確
認してから、再試行してください。
発生場所 D:\Downloads\return_test\ps\ng-pattern_set-exitcode000.ps1:1 文字:1
+ Not-Exists-Command
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Not-Exists-Command:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS D:\Downloads\return_test\ps> $LASTEXITCODE // 戻り値を確認
0
PS D:\Downloads\return_test\ps>
ここまで$LASTEXITCODEを確認しましたが、終了コードを指定していないと変更されず初期値のnull($null)のままとなり、
指定すると、その値が反映されるという結果。
使用用途が違うものの$?と比較すると$LASTEXITCODEの方が分かりやすいですね。
外部コマンドなどと連携する際は$LASTEXITCODEを使った方が良いと思われます。
まとめ
- PowerShell -
$?は- 動作内容:直前のコマンド結果がBoolean(正常:True、異常:False)で反映される
直前のコマンドの結果がBoolean型、TrueorFalseで値がセットされる。 - 使用用途:単純なエラー制御を行う際に使用
単純に直前に実行したコマンドの結果だけ見たいときに使用。
外部コマンド(自作のスクリプトファイルなど)と連携する場合、独特な動きとなりエラー制御が難しいので不向き。
- 動作内容:直前のコマンド結果がBoolean(正常:True、異常:False)で反映される
- PowerShell -
$LASTEXITCODEは- 動作内容:指定した終了コードが反映される
終了コード(exitコマンド、exit 0やexit -1などを指定)が実行された場合、指定された値がセットされる。
終了コードが指定されていない場合、変更されず初期値のnull($null)のままとなる。 - 使用用途:外部コマンドと連携しエラー制御する際に使用
外部コマンド(自作のスクリプトファイルなど)と連携する場合に向いている。
ただし、その外部コマンドで終了コードが指定されていなかった場合は使用できない。
- 動作内容:指定した終了コードが反映される
- (ちなみに)コマンドプロンプト -
%ERRORLEVEL%は- 動作内容:直前のコマンド結果が数値(正常:
0、異常:0以外)で反映される - 使用用途:コマンドプロンプトを扱う上であらゆるエラー制御で使用
- 動作内容:直前のコマンド結果が数値(正常:
関連記事
Discussion