¿Cómo detener un script de PowerShell en el primer error?

250

Quiero que mi script de PowerShell se detenga cuando alguno de los comandos que ejecuto falla (como set -een bash). Estoy usando los comandos de Powershell ( New-Object System.Net.WebClient) y los programas ( .\setup.exe).

Andres Riofrio
fuente

Respuestas:

303

$ErrorActionPreference = "Stop" te llevará a una parte del camino (es decir, esto funciona muy bien para cmdlets).

Sin embargo, para los EXE, tendrá que comprobarse $LastExitCodedespués de cada invocación de exe y determinar si eso falló o no. Desafortunadamente, no creo que PowerShell pueda ayudar aquí porque en Windows, los EXE no son terriblemente consistentes en lo que constituye un código de salida de "éxito" o "fracaso". La mayoría sigue el estándar UNIX de 0 que indica éxito, pero no todos lo hacen. Echa un vistazo a la función CheckLastExitCode en esta publicación de blog . Tu podrias encontrar esto útil.

Keith Hill
fuente
3
¿ $ErrorActionPreference = "Stop"Funciona para programas con buen comportamiento (que devuelven 0 en caso de éxito)?
Andres Riofrio
14
No, no funciona para EXE. Solo funciona para los cmdlets de PowerShell que se ejecutan en proceso. Es un poco doloroso, pero debe verificar $ LastExitCode después de cada invocación de EXE, verificarlo con el código de salida esperado y si esa prueba indica un error, debe lanzar para finalizar la ejecución del script, por ejemplo, throw "$exe failed with exit code $LastExitCode"donde $ exe es solo la ruta a El EXE.
Keith Hill
2
Aceptado porque incluye información sobre cómo hacer que funcione con programas externos.
Andres Riofrio
44
Presento
55
Tenga en cuenta que tiene un psake commandlet llamado "ejecutivo", que puede se puede utilizar para envolver las llamadas a programas externos con un cheque por LastExitCode y mostrará un error (y parada, si se desea)
enorl76
68

Debería poder lograr esto utilizando la declaración $ErrorActionPreference = "Stop"al comienzo de sus scripts.

La configuración predeterminada de $ErrorActionPreferencees Continue, por lo que está viendo que sus scripts continúan después de que ocurran los errores.

goric
fuente
21
Esto no afecta los programas, solo los cmdlets.
Joey
18

Lamentablemente, debido a errores de cmdlets como New-RegKey y Clear-Disk , ninguna de estas respuestas es suficiente. Actualmente me he decidido por las siguientes líneas en la parte superior de cualquier script de PowerShell para mantener mi cordura.

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction']='Stop'

y luego cualquier llamada nativa recibe este tratamiento:

native_call.exe
$native_call_success = $?
if (-not $native_call_success)
{
    throw 'error making native call'
}

Ese patrón de llamadas nativas se está volviendo lo suficientemente común para mí que probablemente debería buscar opciones para hacerlo más conciso. Todavía soy un novato de PowerShell, así que las sugerencias son bienvenidas.

aggieNick02
fuente
14

Necesita un manejo de errores ligeramente diferente para las funciones de PowerShell y para llamar a los exe, y debe asegurarse de informarle al llamador de su script que ha fallado. Sobre la base de Execla biblioteca Psake, un script que tiene la estructura a continuación se detendrá en todos los errores, y se puede usar como plantilla base para la mayoría de los scripts.

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
fuente
Invocando por ejemplo: Exec { sqlite3.exe -bail some.db "$SQL" }la -bailprovoca un error, ya que está tratando de interpretarlo como un parámetro de cmdlet? Envolver cosas entre comillas no parece funcionar. ¿Algunas ideas?
Rcoup
¿Hay alguna manera de poner este código en algún lugar central para que pueda hacer algún tipo de #incluir cuando desee usar el método Exec?
NickG
10

Una ligera modificación a la respuesta de @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

Las diferencias clave aquí son:

  1. utiliza el verbo-sustantivo (imitación Invoke-Command)
  2. implica que usa el operador de llamadas debajo de las cubiertas
  3. imita el -ErrorAction comportamiento de los cmdlets integrados
  4. sale con el mismo código de salida en lugar de lanzar una excepción con un nuevo mensaje
Lucas
fuente
1
¿Cómo pasas parámetros / variables? por ejemploInvoke-Call { dotnet build $something }
Michael Blake
1
@MichaelBlake, la investigación tuya es muy correcta, permitir un paso de parámetros hará que este enfoque sea oro. Estoy inspeccionando adamtheautomator.com/pass-hashtables-invoke-command-argument para ajustar Invoke-Call para admitir el paso de parámetros. Si tengo éxito, lo publicaré como otra respuesta aquí.
Maxim V. Pavlov
¿Por qué utiliza el operador de salpicaduras durante la invocación? ¿Qué te trae eso? & @ScriptBlocky & $ScriptBlockparecen hacer lo mismo. No he podido
buscar
6

Soy nuevo en PowerShell, pero esto parece ser más efectivo:

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

Vine aquí buscando lo mismo. $ ErrorActionPreference = "Stop" mata mi shell inmediatamente cuando prefiero ver el mensaje de error (pausa) antes de que termine. Recurriendo a mis sensibilidades de lote:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Descubrí que esto funciona más o menos igual para mi script ps1 particular:

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

Redirigir stderra stdoutparece que también funciona sin ningún otro comando / envoltorio de bloque de script, aunque no puedo encontrar una explicación de por qué funciona de esa manera ...

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Esto generará lo siguiente como se esperaba (el comando aws s3 ...regresa $LASTEXITCODE = 255)

PS> .\test.ps1

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