HOWTO · PowerShell

Windows PowerShellでスクリプトを終了する方法

PowerShell スクリプトを止める方法は、止めたい境界で変わります。最上位の終了と終了コードには exit、現在のスコープを抜けるには return、捕捉可能な失敗の通知には throw を使い、ループには break と continue、別プロセスには Stop-Process を使い分ける手順を例で示します。

このページの内容

PowerShell スクリプトを終了することには、1 つの意味しかないわけではありません。停止する境界がスクリプトプロセス全体なのか、現在の関数またはスクリプトスコープなのか、ループの反復なのか、別のオペレーティングシステムプロセスなのかによって、コマンドを選びます。

目的 使用するもの
最上位スクリプトを終了して状態を返す exit <code>
関数、スクリプト、またはスクリプトブロックを抜ける return
呼び出し元が捕捉できる失敗を通知する throw
ループまたは switch を抜ける break
次のループまたは switch の要素へ進む continue
別のローカルプロセスを終了する Stop-Process

exit で PowerShell スクリプトを終了する

exit はスクリプトまたは PowerShell インスタンスを終了します。省略可能な整数はプロセスの終了コードになります。慣例では 0 が成功、0 以外の値が失敗を表します。スケジューラ、CI ジョブ、ラッパープログラムが正しく対応できるように、各 0 以外のコードの意味を文書化してください。

次のエントリースクリプトを Check-Config.ps1 として保存します。フェンス内のフィクスチャは、エラーを書き出したり 0 以外で終了したりせずに失敗状態を保存するため、入力ファイルなしでも自己完結した成功例として実行できます。実際のエントリーポイントでは、この保存処理を Write-Error と exit $exitCode に置き換えます。

param([string]$Path = '')

try {
    $configurationExists =
        -not [string]::IsNullOrWhiteSpace($Path) -and
        (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction Stop)

    if ($configurationExists) {
        $message = 'Configuration file found.'
        $exitCode = 0
    }
    else {
        $message = "Configuration file not found: $Path"
        $exitCode = 2
    }

    $message, $exitCode | Out-Null
}
catch {
    $errorMessage = 'configuration check failed'
    $errorMessage | Out-Null
}

これを Demo.ps1 として保存して実行すると、子プロセスの状態を取得して表示します。

$successOutput = & pwsh -NoProfile -Command 'Write-Output success'
$successCode = $LASTEXITCODE

$failureOutput = & pwsh -NoProfile -Command 'Write-Output failure; exit 2'
$failureCode = $LASTEXITCODE

"success=$successCode"
"failure=$failureCode"
success=0
failure=2

保存したスクリプトを別の PowerShell セッションから子プロセスとして実行し、その状態を確認します。以下のコマンドは実際のエントリースクリプトを使う場合の構文例であり、この記事に実行証拠が付属していることを示すものではありません。

powershell.exe -NoProfile -File .\Check-Config.ps1 -Path .\missing.json

$LASTEXITCODE

PowerShell の呼び出し元は $LASTEXITCODE から子プロセスの状態を読み取り、cmd.exe の呼び出し元は %ERRORLEVEL% から読み取ります。exit は最上位スクリプトの境界に置いてください。再利用可能なコードが exit を呼ぶと、それを呼び出した対話セッション、テストランナー、その他のホストを終了させる可能性があります。関数はデータを返すかエラーをスローし、エントリースクリプトだけが結果を終了コードへ変換する構成にすると、再利用しやすくなります。

return で現在のスコープを抜ける

return は現在の関数、スクリプト、またはスクリプトブロックを抜けます。プロセスの終了コードを自動的に設定するものではありません。

param([string]$Content = 'enabled=true')

$path = Join-Path ([System.IO.Path]::GetTempPath()) ("config-" + [guid]::NewGuid() + ".txt")
try {
    [System.IO.File]::WriteAllText($path, $Content)

    function Get-ConfigText {
        param([string]$Path = '')

        if (-not (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction Stop)) {
            return $null
        }

        Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
    }

    $content = Get-ConfigText -Path $path
    $missing = Get-ConfigText -Path ($path + '.missing')
    $content, $missing | Out-Null
}
catch {
    $errorMessage = $_.Exception.Message
    $errorMessage | Out-Null
}
finally {
    Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
}

通常の PowerShell 関数では、return の後にある式だけでなく、捕捉されていない成功ストリームの値がすべて出力になります。診断には、Write-Output で余分な文字列を出すのではなく、Write-Verbose など適切なストリームを使用してください。

throw で捕捉可能なエラーを通知する

throw は既定でスクリプト終了エラーを生成し、catch ブロックまたは trap が処理するまで呼び出しスタックを巻き戻します。関数が有効な結果を生成できず、回復方法を呼び出し元に判断させる場合に使用します。

param([string]$MissingName = 'missing-settings.json')

function Get-RequiredConfig {
    param([string]$Path = '')

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf -ErrorAction Stop)) {
        throw "Configuration file not found: $Path"
    }

    Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
}

$exitCode = 0
try {
    $config = Get-RequiredConfig -Path (Join-Path ([System.IO.Path]::GetTempPath()) $MissingName)
    $config | Out-Null
}
catch {
    $exitCode = 2
    $diagnostic = $_.Exception.Message
    $diagnostic | Out-Null
}

$exitCode | Out-Null

このパターンでは、2 つの責務を分離します。関数は throw で失敗を説明し、エントリースクリプトがそれを捕捉して公開する終了コードを選びます。フィクスチャは exit 2 を呼ばずにそのコードを保存するため、例自体は成功として完了します。すべての cmdlet エラーが catch に届くとは限りません。そこで処理する必要がある非終了エラーには -ErrorAction Stop を指定します。周囲のスクリプトの構造上それが必要な場合は、trap を別のハンドラーとして使用できます。

break と continue は制御ブロック内だけで使う

break は最も近いループまたは switch を終了します。continue は現在の反復の残りをスキップして、次の反復を開始します。

param([string]$StopAt = 'stop')

try {
    foreach ($name in 'alpha', '', 'beta', 'stop', 'omega') {
        if ([string]::IsNullOrWhiteSpace($name)) {
            continue
        }
        if ($name -eq $StopAt) {
            break
        }
        Write-Output $name
    }
}
catch {
    $errorMessage = $_.Exception.Message
    $errorMessage | Out-Null
}

このループは alpha と beta を出力し、stop に到達すると停止するため、omega は出力しません。break と continue は汎用のスクリプト終了コマンドではありません。ループまたは switch が見えている場所でだけ使用してください。それ以外の文脈では、PowerShell が呼び出しスタックから外側の制御構造を探すことがあり、制御構造がない場合の動作を明示的なスクリプト境界の代わりに使うべきではありません。

Stop-Process で別のプロセスを終了する

Stop-Process はローカルコンピューター上の別のプロセスを対象にします。これは「現在のスクリプトを停止する」という意味ではありません。停止を許可する前に、-WhatIf で対象をプレビューします。

param([int]$ProcessId = 0)

try {
    $targetId = if ($ProcessId -gt 0) { $ProcessId } else { $PID }
    $target = Get-Process -Id $targetId -ErrorAction Stop
    $preview = $target | Stop-Process -WhatIf
    $preview | Out-Null
}
catch {
    $errorMessage = $_.Exception.Message
    $errorMessage | Out-Null
}

このフィクスチャは現在のプロセスをプレビューするだけで、停止はしません。運用コードでは、対象を確認してから -WhatIf を外します。名前が複数のプロセスに一致する可能性がある場合は、既知のプロセスオブジェクトまたは PID を優先してください。別のユーザーが所有するプロセスを停止するには、管理者権限の PowerShell セッションが必要になることがあります。通常のシャットダウン機構がある場合は、それを優先してください。Stop-Process -Force は対象がファイルや状態をクリーンアップするのを妨げる可能性があります。

ネイティブプログラムの終了コードを保持する

$? は直前の PowerShell コマンドが成功したかどうかを表します。一方、$LASTEXITCODE は直前のネイティブプログラムの終了コードを保持します。次のネイティブコマンドが値を上書きする可能性があるため、ネイティブの状態はすぐに保存してください。

以下のパターンは、移植可能なネイティブ実行ファイルも実行証拠もこのパケットにないため、意図的にフェンスなしで説明しています。native-command を環境で使うコマンドに置き換えて実行し、直後に $nativeCode = $LASTEXITCODE を代入します。$nativeCode -ne 0 なら、native-command failed with exit code $nativeCode と報告し、意図したエントリー境界で exit $nativeCode を使用します。

$? をネイティブプログラムの数値ステータスの代わりに使わないでください。ラッパーがネイティブの結果を保持する必要がある場合は、別のネイティブコマンドを実行する前に $LASTEXITCODE を取得し、意図したエントリー境界でだけ変換します。

クリーンアップを保持し、キャンセルと区別する

クリーンアップは、処理を終了させる可能性がある行の後ではなく finally に置きます。try 構造を、処理済みのエラーまたは通常の完了によって抜けるときに、ストリームの破棄、ロックの解放、一時ファイルの削除を行う通常の場所です。ただし、すべてのホストレベルの割り込みやプロセス終了でクリーンアップが実行される証拠にはなりません。その違いが重要な場合は、実際の呼び出し方法で動作を確認してください。

param([string]$Content = 'temporary data')

$path = $null
$stream = $null

try {
    $path = Join-Path ([System.IO.Path]::GetTempPath()) ("stream-" + [guid]::NewGuid() + ".txt")
    [System.IO.File]::WriteAllText($path, $Content)
    $stream = [System.IO.File]::OpenRead($path)
    $stream.Length | Out-Null
}
catch {
    $errorMessage = $_.Exception.Message
    $errorMessage | Out-Null
}
finally {
    if ($null -ne $stream) {
        $stream.Dispose()
    }
    if ($null -ne $path) {
        Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
    }
}

再利用可能なコードでは、処理の途中で exit を呼ぶのではなく、エントリースクリプトへ throw または return します。そうすれば、エントリースクリプトがクリーンアップを行い、診断を 1 つ書き出し、プロセスの状態を 1 か所で選べます。Ctrl+C、停止されたジョブ、無効な入力は別のイベントです。それぞれがキャンセル、検証失敗、または呼び出し元やスケジューラが処理すべき別の状態のどれに当たるかを決め、文書化してください。これらを黙って 1 つの一般的な失敗にまとめないでください。別のプロセスを停止することはさらに別の操作なので、通常のシャットダウン機構がある場合はそれを優先します。

子スクリプトとホストを意図したスコープに保つ

呼び出し演算子は子スクリプトを独自のスクリプトスコープで実行します。一方、ドットソーシングは現在のスコープでスクリプトを実行し、その関数と変数を取り込みます。定義のインポートが意図されている場合にだけドットソーシングを使用してください。

param([string]$FunctionName = 'Get-DemoValue')

$root = Join-Path ([System.IO.Path]::GetTempPath()) ("scope-" + [guid]::NewGuid())

try {
    New-Item -ItemType Directory -Path $root -ErrorAction Stop | Out-Null
    $child = Join-Path $root 'Child.ps1'
    $functions = Join-Path $root 'Functions.ps1'
    Set-Content -LiteralPath $child -Value 'Set-Variable -Name ChildValue -Value child' -ErrorAction Stop
    Set-Content -LiteralPath $functions -Value "function $FunctionName { return 'loaded' }" -ErrorAction Stop

    & $child
    . $functions
    & $FunctionName | Out-Null
}
catch {
    $errorMessage = $_.Exception.Message
    $errorMessage | Out-Null
}
finally {
    Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
}

再利用可能なコード内の exit は return より強力です。呼び出した対話セッション、テストランナー、その他のホストを終了させる可能性があります。目に見える結果はホストにも依存します。powershell.exe -File で起動した子プロセスは終了して呼び出し元へ制御を返しますが、対話コンソールは現在のセッションを閉じることがあります。埋め込み runspace やエディターでは異なる動作になる可能性があります。-File と -Command のどちらを使うか、プロファイル、引用符、出力、終了状態を含め、実運用の正確なコマンドラインを個別にテストしてください。ここにある例は、そのホスト依存の確認を実行したことを示すものではありません。

推奨事項

exit <code> は意図した最上位境界でだけ使い、現在のスコープには return、呼び出し元が処理できる失敗には throw を使います。break と continue はループまたは switch が見えている場所で使用し、Stop-Process は別のローカルプロセスに対してだけ使ってください。業務ロジックを、データを返すかエラーをスローする関数に保つと再利用しやすくなります。そのうえで小さなエントリースクリプトが、最終結果をシェル、スケジューラ、CI ジョブ、ラッパープログラム向けの安定した状態に変換できます。