我希望我的 PowerShell 脚本在我运行的任何命令失败时停止(例如 bash 中的 set -e
)。我正在使用 Powershell 命令 (New-Object System.Net.WebClient
) 和程序 (.\setup.exe
)。
$PSNativeCommandUseErrorActionPreference
设置为 $true
并将 $ErrorActionPreference
设置为 'Stop'
可以在发生本机命令错误时停止脚本执行。测试代码: & { $PSNativeCommandUseErrorActionPreference = $true; $ErrorActionPreference = 'Stop'; ping.exe bbb; ping.exe aaa }
。
$ErrorActionPreference = "Stop"
将帮助您实现这一目标(即这对 cmdlet 非常有效)。
但是对于 EXE,您需要在每次 exe 调用后自己检查 $LastExitCode
并确定是否失败。不幸的是,我不认为 PowerShell 在这里可以提供帮助,因为在 Windows 上,EXE 在“成功”或“失败”退出代码的构成方面并不是非常一致。大多数遵循 0 表示成功的 UNIX 标准,但并非所有人都这样做。查看 CheckLastExitCode function in this blog post。你可能会发现它很有用。
您应该能够通过在脚本开头使用语句 $ErrorActionPreference = "Stop"
来完成此操作。
$ErrorActionPreference
的默认设置是 Continue
,这就是您看到脚本在发生错误后继续运行的原因。
可悲的是,due to buggy cmdlets like New-RegKey and Clear-Disk,这些答案都不够。我目前在名为 ps_support.ps1
的文件中确定了以下代码:
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction']='Stop'
function ThrowOnNativeFailure {
if (-not $?)
{
throw 'Native Failure'
}
}
然后在任何 powershell 文件中,在文件的 CmdletBinding
和 Param
之后(如果存在),我有以下内容:
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\ps_support.ps1"
重复的 ErrorActionPreference = "Stop"
行是故意的。如果我犯了错误并且不知何故弄错了 ps_support.ps1
的路径,那不需要默默地失败!
我将 ps_support.ps1
保存在我的存储库/工作区的公共位置,因此点源的路径可能会根据当前 .ps1
文件的位置而改变。
任何本机调用都会得到这种处理:
native_call.exe
ThrowOnNativeFailure
将该文件添加到 dot-source 帮助我在编写 powershell 脚本时保持理智。 :-)
对于 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
}
Exec { sqlite3.exe -bail some.db "$SQL" }
,-bail
会导致错误,因为它试图将其解释为 Cmdlet 参数?用引号括起来似乎不起作用。有任何想法吗?
powershell-error-handling-sanity.ps1
的文件,然后使用 . <relative_or_absolute_path_to_powershell-error-handling-sanity.ps1
将 ps1 文件点源到任何其他 ps1 文件的顶部
@alasairtree 对 answer 的轻微修改:
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
这里的主要区别是:
它使用动词名词(模仿 Invoke-Command)意味着它在幕后使用调用运算符模仿内置 cmdlet 的 -ErrorAction 行为以相同的退出代码退出,而不是用新消息引发异常
Invoke-Call { dotnet build $something }
& @ScriptBlock
和 & $ScriptBlock
似乎做同样的事情。无法谷歌在这种情况下有什么区别
我是 powershell 新手,但这似乎是最有效的:
doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
我来这里是为了寻找同样的东西。 $ErrorActionPreference="Stop" 当我宁愿在它终止之前看到错误消息(暂停)时,它会立即杀死我的 shell。回到我的批次敏感性:
IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF
我发现这对于我的特定 ps1 脚本来说几乎是一样的:
Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}
似乎简单的重新抛出就可以了。
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
}
现在输出 Completed 仅在成功执行后出现。
对于 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
将 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
$ErrorActionPreference = "Stop"
是否适用于表现良好的程序(成功时返回 0)?throw "$exe failed with exit code $LastExitCode"
,其中 $exe 只是EXE 的路径。