ChatGPT解决这个技术问题 Extra ChatGPT

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

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

在 PowerShell 7.3.0-preview.1 中,将 $PSNativeCommandUseErrorActionPreference 设置为 $true 并将 $ErrorActionPreference 设置为 'Stop' 可以在发生本机命令错误时停止脚本执行。测试代码: & { $PSNativeCommandUseErrorActionPreference = $true; $ErrorActionPreference = 'Stop'; ping.exe bbb; ping.exe aaa }

g
gunr2171

$ErrorActionPreference = "Stop" 将帮助您实现这一目标(即这对 cmdlet 非常有效)。

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


$ErrorActionPreference = "Stop" 是否适用于表现良好的程序(成功时返回 0)?
不,它对 EXE 根本不起作用。它仅适用于在进程内运行的 PowerShell cmdlet。这有点痛苦,但您必须在每次 EXE 调用后检查 $LastExitCode,检查预期的退出代码,如果该测试表明失败,您必须抛出以终止脚本的执行,例如 throw "$exe failed with exit code $LastExitCode",其中 $exe 只是EXE 的路径。
接受,因为它包含有关如何使其与外部程序一起使用的信息。
我在这里提出了一个关于它的功能请求:connect.microsoft.com/PowerShell/feedback/details/751703/…
请注意,psake 有一个名为“exec”的命令行开关,您可以使用它来包装对外部程序的调用,并检查 LastExitCode 并显示错误(如果需要,可以停止)
g
goric

您应该能够通过在脚本开头使用语句 $ErrorActionPreference = "Stop" 来完成此操作。

$ErrorActionPreference 的默认设置是 Continue,这就是您看到脚本在发生错误后继续运行的原因。


这不会影响程序,只会影响 cmdlet。
a
aggieNick02

可悲的是,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 文件中,在文件的 CmdletBindingParam 之后(如果存在),我有以下内容:

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

重复的 ErrorActionPreference = "Stop" 行是故意的。如果我犯了错误并且不知何故弄错了 ps_support.ps1 的路径,那不需要默默地失败!

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

任何本机调用都会得到这种处理:

native_call.exe
ThrowOnNativeFailure

将该文件添加到 dot-source 帮助我在编写 powershell 脚本时保持理智。 :-)


a
alastairtree

对于 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 参数?用引号括起来似乎不起作用。有任何想法吗?
有没有办法将此代码放在中心位置,以便在您想要使用 Exec 方法时执行某种#include?
是的,你可以。您可以将其放入名为 powershell-error-handling-sanity.ps1 的文件,然后使用 . <relative_or_absolute_path_to_powershell-error-handling-sanity.ps1 将 ps1 文件点源到任何其他 ps1 文件的顶部
L
Lucas

@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 }
@MichaelBlake您的询问非常正确,允许参数传递将使这种方法成为黄金。我正在检查 adamtheautomator.com/pass-hashtables-invoke-command-argument 以调整 Invoke-Call 以支持参数传递。如果我成功了,我会在这里发布它作为另一个答案。
为什么在调用过程中使用 splatting 运算符?这对你有什么好处? & @ScriptBlock& $ScriptBlock 似乎做同样的事情。无法谷歌在这种情况下有什么区别
P
Peter L

我是 powershell 新手,但这似乎是最有效的:

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

h
harvey263

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

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

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

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

J
Jarekczek

似乎简单的重新抛出就可以了。

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 仅在成功执行后出现。


N
Nasreddine Galfout

对于 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

u
ubi

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