如何在出现第一个错误时停止PowerShell脚本?

我希望我的PowerShell脚本在我运行的任何命令失败时停止(如bash中的set -e)。我使用Powershell命令(New-Object System.Net.WebClient)和程序(.\setup.exe)。

224156 次浏览

你应该能够通过在脚本开头使用语句$ErrorActionPreference = "Stop"来实现这一点。

$ErrorActionPreference的默认设置是Continue,这就是为什么你会看到你的脚本在错误发生后继续运行。

$ErrorActionPreference = "Stop"将让你在那里的一部分(即这对cmdlet非常有用)。

然而,对于exe,你需要在每次exe调用后自己检查$LastExitCode,并确定是否失败。不幸的是,我不认为PowerShell可以在这里提供帮助,因为在Windows上,EXEs对于“成功”或“失败”退出码的构成并不是非常一致。大多数遵循UNIX标准,0表示成功,但并非所有都这样做。检查CheckLastExitCode函数。你可能会发现它很有用。

遗憾的是,由于有bug的cmdlet,如New-RegKey和Clear-Disk,这些答案都不够。我目前在一个名为ps_support.ps1的文件中确定了以下代码:

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction']='Stop'
function ThrowOnNativeFailure {
if (-not $?)
{
throw 'Native Failure'
}
}

然后在任何powershell文件中,在该文件的CmdletBindingParam之后(如果存在),我有以下内容:

$ErrorActionPreference = "Stop"
. "$PSScriptRoot\ps_support.ps1"

重复的ErrorActionPreference = "Stop"行是有意的。如果我做了傻事,以某种方式得到了ps_support.ps1的路径错误,它需要不是无声失败!

我将ps_support.ps1保存在我的repo/workspace的公共位置,因此点源的路径可能会根据当前.ps1文件的位置而改变。

任何本地调用都会得到这样的处理:

native_call.exe
ThrowOnNativeFailure

在编写powershell脚本时,使用该文件进行点源可以帮助我保持头脑清醒。:-)

我来这里也是为了寻找同样的东西。$ErrorActionPreference="Stop"立即杀死我的shell时,我宁愿看到错误消息(暂停)之前,它终止。回到我的批处理敏感性:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

我发现这对我的特定ps1脚本几乎是一样的:

Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}

对于powershell函数和调用exe函数,您需要稍微不同的错误处理,并且您需要确保告诉脚本的调用者它失败了。构建在Psake库的Exec之上,具有下面结构的脚本将在所有错误时停止,并可作为大多数脚本的基本模板。

Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"




# Taken from psake https://github.com/psake/psake
<#
.SYNOPSIS
This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
to see if an error occcured. If an error is detected then an exception is thrown.
This function allows you to run command-line programs without having to
explicitly check the $lastexitcode variable.
.EXAMPLE
exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
#>
function Exec
{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
[Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd)
)
& $cmd
if ($lastexitcode -ne 0) {
throw ("Exec: " + $errorMessage)
}
}


Try {


# Put all your stuff inside here!


# powershell functions called as normal and try..catch reports errors
New-Object System.Net.WebClient


# call exe's and check their exit code using Exec
Exec { setup.exe }


} Catch {
# tell the caller it has all gone wrong
$host.SetShouldExit(-1)
throw
}

对@alastairtree中的回答进行了轻微修改:

function Invoke-Call {
param (
[scriptblock]$ScriptBlock,
[string]$ErrorAction = $ErrorActionPreference
)
& @ScriptBlock
if (($lastexitcode -ne 0) -and $ErrorAction -eq "Stop") {
exit $lastexitcode
}
}


Invoke-Call -ScriptBlock { dotnet build . } -ErrorAction Stop

这里的关键区别是:

  1. 它使用动词-名词(模仿Invoke-Command)
  2. 暗示它在封面下使用调用操作符
  3. 来自内置cmdlet的模仿 -ErrorAction行为
  4. 使用相同的退出代码退出,而不是使用新消息抛出异常

我刚接触powershell,但这似乎是最有效的:

doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}

stderr重定向到stdout似乎也没有任何其他命令/脚本块包装器,尽管我找不到一个解释为什么它是这样工作的。

# test.ps1


$ErrorActionPreference = "Stop"


aws s3 ls s3://xxx
echo "==> pass"


aws s3 ls s3://xxx 2>&1
echo "shouldn't be here"

这将如预期的那样输出以下内容(命令aws s3 ...返回$LASTEXITCODE = 255)

PS> .\test.ps1


An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass

看来简单的重掷就行了。

param ([string] $Path, [string] $Find, [string] $Replace)
try {
((Get-Content -path $Path -Raw) -replace $Find, $Replace) | Set-Content -Path $Path
Write-Output Completed.
} catch {
# Without try/catch block errors don't interrupt program flow.
throw
}

现在只有在成功执行之后才会出现完成输出。

对于2021年来到这里的人,这是我的解决方案,涵盖了cmdlet和程序

function CheckLastExitCode {
param ([int[]]$SuccessCodes = @(0))


if (!$?) {
Write-Host "Last CMD failed" -ForegroundColor Red
#GoToWrapperDirectory in my code I go back to the original directory that launched the script
exit
}


if ($SuccessCodes -notcontains $LastExitCode) {
Write-Host "EXE RETURNED EXIT CODE $LastExitCode" -ForegroundColor Red
#GoToWrapperDirectory in my code I go back to the original directory that launched the script
exit
}
    

}

你可以这样用

cd NonExistingpath
CheckLastExitCode

据我所知,Powershell对它调用的子程序返回的非零退出码没有任何自动处理。

到目前为止,我所知道的模仿bash -e行为的唯一解决方案是在外部命令调用每一个后添加这个检查:

if(!$?) { Exit $LASTEXITCODE }