HOWTO · PowerShell

如何在 Windows PowerShell 中终止脚本

了解何时使用 exit、return、throw、break、continue 或 Stop-Process,终止正确的作用域并返回可靠的 PowerShell 退出代码。

本页内容

终止 PowerShell 脚本并不是一种固定操作。你需要先确定要停止的是整个脚本进程、当前函数、一次循环迭代,还是另一个操作系统进程,然后选择对应的语句。

目的 使用方式
结束顶层脚本并报告状态 exit <代码>
退出函数、脚本或脚本块 return
报告可由调用方捕获的错误 throw
退出循环或 switch break
跳到下一个循环或 switch continue
终止另一个本地进程 Stop-Process

使用 exit 终止 PowerShell 脚本

exit 会结束脚本或 PowerShell 实例。可选的整数参数会成为进程退出代码。按照惯例,0 表示成功,非零值表示失败。应记录每个错误代码的含义,以便计划任务、CI 作业和包装程序正确处理结果。

将下面的入口脚本保存为 Check-Config.ps1

param([Parameter(Mandatory)][string]$Path)

if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
    Write-Error "Configuration file not found: $Path"
    exit 2
}

Write-Output 'Configuration file found.'
exit 0

从另一个 PowerShell 会话把它作为子进程运行,然后读取状态:

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

最后一条命令输出 2。在 PowerShell 7 中,应使用 pwsh 代替 powershell.exe。PowerShell 调用方通过 $LASTEXITCODE 读取子进程状态;cmd.exe 调用方则读取 %ERRORLEVEL%

最好只在顶层脚本边界使用 exit。如果可复用函数调用 exit,它可能会关闭交互式会话、测试运行器或其他调用该函数的宿主。函数应返回数据或引发错误,只有入口脚本负责把结果转换为退出代码。

使用 return 退出当前作用域

return 会退出当前函数、脚本或脚本块,但它本身不会设置进程退出代码。

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

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

    Get-Content -LiteralPath $Path -Raw
}

在普通 PowerShell 函数中,所有未捕获的成功流值都会成为输出,而不仅是 return 后面的表达式。诊断消息应写入 Write-Verbose 等合适的流,不要用 Write-Output 发送额外字符串,以免改变函数结果。

使用 throw 报告可捕获的错误

throw 默认会产生终止脚本的错误,并展开调用堆栈,直到 catch 块或 trap 对它进行处理。当函数无法生成有效结果,并且应由调用方决定如何恢复时,可以使用它。

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

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

    Get-Content -LiteralPath $Path -Raw
}

try {
    $config = Get-RequiredConfig -Path '.\settings.json'
}
catch {
    Write-Error $_
    exit 2
}

这种模式分离了职责:函数通过 throw 说明失败原因,入口脚本捕获错误并选择对外的退出代码。并非所有 cmdlet 错误都会自动进入 catch;如果需要在那里处理非终止错误,请为相应命令添加 -ErrorAction Stop

仅在控制块中使用 breakcontinue

break 会退出最近的循环或 switchcontinue 会跳过当前迭代的剩余部分,并开始下一次迭代。

foreach ($name in 'alpha', '', 'beta', 'stop', 'omega') {
    if ([string]::IsNullOrWhiteSpace($name)) {
        continue
    }
    if ($name -eq 'stop') {
        break
    }
    Write-Output $name
}

输出:

alpha
beta

它们不是通用的脚本终止命令。在循环、switchtrap 之外使用时,PowerShell 可能会沿调用堆栈查找外层控制结构;如果找不到,当前运行空间可能被终止。

使用 Stop-Process 终止另一个进程

Stop-Process 针对本地计算机上的另一个进程,它并不表示“停止当前脚本”。使用较宽泛的匹配条件前,先通过 -WhatIf 预览目标:

Get-Process -Name notepad -ErrorAction SilentlyContinue |
    Stop-Process -WhatIf

确认目标之后再移除 -WhatIf。当一个名称可能匹配多个进程时,优先使用已知的进程对象或 PID。终止其他用户拥有的进程还可能需要以管理员身份运行 PowerShell。

保留本机程序的退出代码

$? 表示上一条 PowerShell 命令是否成功,而 $LASTEXITCODE 保存最近一个本机程序的退出代码。下一条本机命令可能覆盖该值,因此应立即保存。

git status --porcelain
$gitCode = $LASTEXITCODE

if ($gitCode -ne 0) {
    Write-Error "git failed with exit code $gitCode"
    exit $gitCode
}

在可复用逻辑中使用 returnthrow,把 exit <代码> 保留在顶层脚本边界。这样可以清楚地区分函数输出、控制流和操作系统进程状态。

保证清理并区分取消操作

应将清理工作放在 finally 中,而不是放在可能终止处理的语句之后。根据 PowerShell 语言关键字文档,无论 try 中的工作成功、错误到达 catch、调用 exit,还是 Ctrl+C 中断脚本,finally 都会执行,因此适合释放流、锁或临时文件。

$stream = $null

try {
    $stream = [System.IO.File]::OpenRead($Path)
    # Process the stream.
}
finally {
    if ($null -ne $stream) {
        $stream.Dispose()
    }
}

让可复用代码通过 throwreturn 回到入口脚本,而不要在处理中间调用 exit。这样入口脚本可在一个明确位置完成清理、写入诊断并选择进程状态。Ctrl+C、被停止的作业和无效输入是不同事件:应决定并记录每一种是取消、验证失败还是调用方或计划程序必须处理的其他状态。不要悄悄把它们归为同一种通用失败。终止另一个进程又是另一回事;应优先使用应用的正常关闭方式。Stop-Process -Force 可能阻止目标清理文件或状态,只有了解这一后果时才使用。

让子脚本和宿主处于预期作用域

调用运算符会在子脚本自己的脚本作用域中运行它;点源会在当前作用域中运行脚本并导入其函数和变量。仅在有意导入定义时使用点源。

# Run the child in its own script scope.
& .\Child.ps1

# Import definitions into the current scope.
. .\Functions.ps1

可复用代码中的 exitreturn 更强,它可能关闭交互式会话、测试运行器或其他宿主。用 powershell.exe -Filepwsh -File 启动的子进程会把控制权交还调用方;交互式控制台可能关闭当前会话。嵌入式运行空间和编辑器的行为可能不同。应分别测试实际生产命令行中的 -File-Command、配置文件、引号、输出和退出状态。

建议

只在有意的顶层边界使用 exit <代码>,用 return 离开当前作用域,用 throw 报告调用方可处理的失败。只在可见的循环或 switch 中使用 breakcontinue,并且仅用 Stop-Process 终止独立的本地进程。返回数据或抛出错误的函数可保持复用;小型入口脚本可将结果转换为 shell、计划程序、CI 或包装程序可用的稳定状态。