🐸

PowerShell 自動変数“$?”と“$LASTEXITCODE”の違いを検証

2023/08/22に公開

概要

PowerShellではじめてスクリプトを作成した当時、エラー制御(エラーハンドリング)でハマってしまいました。

よくネット記事のサンプルコードやサンプルプログラムに登場する自動変数$?ですが、
外部コマンドと連携した際、使い方を間違えてしまい思わぬ挙動になってしまいました。

コマンドプロンプトのエラー制御は%ERRORLEVEL%のみでエラー制御しますが、
PowerShellでは、$?以外に$LASTEXITCODEも合わせて使用する必要がある事がわかりました。

今回、PowerShellの自動変数、$?$LASTEXITCODEの挙動を直接コマンド(CLI・コマンドレット)と 外部コマンド[外だし自作のスクリプトファイル(ps1ファイル)を使用]の2パターンで検証してみた結果を紹介します。

この記事のターゲット

  • PowerShellの初心者の方
  • エラー制御がうまくいかず悩んでいる方
  • 自動変数の$?$LASTEXITCODEの違いを詳しく知りたい方
  • コマンドプロンプト(cmd)の%ERRORLEVEL%とも動作を比較したい方

環境

PowerShellのバージョン
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 0exit -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>
ok-pattern.batの中身
ECHO CMD-OKAY!

ng-pattern.batの中身
NOT-EXISTS-COMMAND

ng-pattern_set-exitcode404.batの中身
NOT-EXISTS-COMMAND
EXIT /B 404

戻り値(%ERRORLEVEL%)の検証

以下の検証結果からコマンドプロンプトで実行した場合、直接コマンドで打つ時や外部コマンドを打つ時など、
どのシチュエーションにおいても直前のコマンド結果(終了コードを指定した場合は、指定した値)が%ERRORLEVEL%に反映される。
※一部、%ERRORLEVEL%に値がセットされないコマンドあり(DELコマンドなど)

直接コマンドを実行した場合(%ERRORLEVEL%)

OKパターンの戻り値を確認
C:\WINDOWS\system32>ECHO CMD-OKAY!                                  // 直接コマンドを実行(存在するコマンド)
CMD-OKAY!

C:\WINDOWS\system32>ECHO %ERRORLEVEL%                               // 戻り値を確認
0
NGパターンの戻り値を確認
C:\WINDOWS\system32>NOT-EXISTS-COMMAND                              // 直接コマンドを実行(存在しないコマンド)
'NOT-EXISTS-COMMAND' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

C:\WINDOWS\system32>
C:\WINDOWS\system32>ECHO %ERRORLEVEL%                               // 戻り値を確認
9009

C:\WINDOWS\system32>

外部コマンド(自作のバッチファイル)を実行した場合

OKパターンの戻り値を確認
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
NGパターンの戻り値を確認
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>
NGパターン(+終了コード=404で指定)の戻り値を確認
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>
ok-pattern.ps1の中身
Write-Host "PS-OKAY!"

ok-pattern_set-exitcode000.ps1の中身
Write-Host "PS-OKAY!"
exit 0

ng-pattern.ps1の中身
Not-Exists-Command

ng-pattern_set-exitcode404.ps1の中身
Not-Exists-Command
exit 404

ng-pattern_set-exitcode000.ps1の中身
Not-Exists-Command
exit 0

戻り値($?)の検証

以下の検証結果からPowerShellの戻り値(自動変数)$?では、直接コマンドを実行すると直前のコマンド結果が「 True or False 」で反映されることがわかった。

また、外部コマンドを実行した際、終了コードを指定しないと正常値の「 True 」で反映、
終了コードを0で指定すると正常値の「 True 」、0以外で指定すると異常値の「 False」が反映されるもよう。

直接コマンドを実行した場合($?)

OKパターンの戻り値を確認
PS C:\WINDOWS\system32> Write-Host "PS-OKAY!"                       // 直接コマンドを実行
PS-OKAY!
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $?                                          // 戻り値を確認
True
PS C:\WINDOWS\system32>
NGパターンの戻り値を確認
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>

外部コマンド(自作のスクリプトファイル)を実行した場合

OKパターンの戻り値を確認
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>
OKパターン(+終了コード=0で指定)の戻り値を確認
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>
NGパターンの戻り値を確認
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>
NGパターン(+終了コード=404で指定)の戻り値を確認
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で指定してみて実行するとどうなるか下記で確認してみました。

NGパターン(+終了コード=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)

OKパターンの戻り値を確認
PS C:\WINDOWS\system32> Write-Host "PS-OKAY!"                       // 直接コマンドを実行
PS-OKAY!
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> $LASTEXITCODE                               // 戻り値を確認
PS C:\WINDOWS\system32>
NGパターンの戻り値を確認
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>

外部コマンド(スクリプトファイル)を実行した場合

OKパターンの戻り値を確認
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>
OKパターン(+終了コード=0で指定)の戻り値を確認
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>
NGパターンの戻り値を確認
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>
NGパターン(+終了コード=404で指定)の戻り値を確認
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>
NGパターン(+終了コード=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> $LASTEXITCODE                       // 戻り値を確認
0
PS D:\Downloads\return_test\ps>

ここまで$LASTEXITCODEを確認しましたが、終了コードを指定していないと変更されず初期値のnull($null)のままとなり、
指定すると、その値が反映されるという結果。

使用用途が違うものの$?と比較すると$LASTEXITCODEの方が分かりやすいですね。
外部コマンドなどと連携する際は$LASTEXITCODEを使った方が良いと思われます。

まとめ

  • PowerShell - $?
    • 動作内容:直前のコマンド結果がBoolean(正常:True、異常:False)で反映される
      直前のコマンドの結果がBoolean型、True or Falseで値がセットされる。
    • 使用用途:単純なエラー制御を行う際に使用
      単純に直前に実行したコマンドの結果だけ見たいときに使用。
      外部コマンド(自作のスクリプトファイルなど)と連携する場合、独特な動きとなりエラー制御が難しいので不向き。
  • PowerShell - $LASTEXITCODE
    • 動作内容:指定した終了コードが反映される
      終了コード(exitコマンド、exit 0exit -1などを指定)が実行された場合、指定された値がセットされる。
      終了コードが指定されていない場合、変更されず初期値のnull($null)のままとなる。
    • 使用用途:外部コマンドと連携しエラー制御する際に使用
      外部コマンド(自作のスクリプトファイルなど)と連携する場合に向いている。
      ただし、その外部コマンドで終了コードが指定されていなかった場合は使用できない。
  • (ちなみに)コマンドプロンプト - %ERRORLEVEL%
    • 動作内容:直前のコマンド結果が数値(正常:0、異常:0以外)で反映される
    • 使用用途:コマンドプロンプトを扱う上であらゆるエラー制御で使用

関連記事

https://haretokidoki-blog.com/pasocon_powershell-startup/
https://zenn.dev/haretokidoki/articles/7e6924ff0cc960

GitHubで編集を提案

Discussion