Como parar um script do PowerShell no primeiro erro?

250

Quero que meu script do PowerShell seja interrompido quando qualquer um dos comandos executados falhar (como set -eno bash). Estou usando os comandos do Powershell ( New-Object System.Net.WebClient) e os programas ( .\setup.exe).

Andres Riofrio
fonte

Respostas:

303

$ErrorActionPreference = "Stop" você fará parte do caminho até lá (isto é, funciona muito bem para cmdlets).

No entanto, para EXEs, você precisará verificar a $LastExitCodesi mesmo após cada chamada de exe e determinar se isso falhou ou não. Infelizmente, acho que o PowerShell não pode ajudar aqui, porque no Windows, os EXEs não são terrivelmente consistentes com o que constitui um código de saída de "sucesso" ou "falha". A maioria segue o padrão UNIX de 0, indicando sucesso, mas nem todos o fazem. Confira a função CheckLastExitCode nesta postagem do blog . Você pode achar util.

Keith Hill
fonte
3
Funciona $ErrorActionPreference = "Stop"para programas bem comportados (que retornam 0 em caso de sucesso)?
Andres Riofrio
14
Não, não funciona de maneira alguma para EXEs. Ele funciona apenas para cmdlets do PowerShell que são executados em processo. É meio doloroso, mas você deve verificar $ LastExitCode após cada chamada de EXE, verificar se o código de saída esperado e se esse teste indicar falha, você deve executar para finalizar a execução do script, por exemplo, throw "$exe failed with exit code $LastExitCode"onde $ exe é apenas o caminho para o EXE.
Keith Hill
2
Aceito porque inclui informações sobre como fazê-lo funcionar com programas externos.
Andres Riofrio
4
Coloquei uma solicitação de recurso sobre isso aqui: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant
5
nota que psake tem uma commandlet chamado de "exec", que pode ser usado para embrulhar as chamadas para programas externos, com um cheque de LastExitCode e exibir um erro (e parada, se desejado)
enorl76
68

Você deve conseguir isso usando a instrução $ErrorActionPreference = "Stop"no início de seus scripts.

A configuração padrão de $ErrorActionPreference é Continue, e é por isso que você vê seus scripts continuarem após a ocorrência de erros.

goric
fonte
21
Isso não afeta os programas, apenas os cmdlets.
Joey
18

Infelizmente, devido a cmdlets com erros como New-RegKey e Clear-Disk , nenhuma dessas respostas é suficiente. Atualmente, eu decidi as seguintes linhas no topo de qualquer script do PowerShell para manter minha sanidade.

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

e qualquer chamada nativa recebe este tratamento:

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

Esse padrão de chamada nativa está lentamente se tornando comum o suficiente para mim, para que eu provavelmente deva procurar opções para torná-lo mais conciso. Eu ainda sou um novato em PowerShell, então sugestões são bem-vindas.

aggieNick02
fonte
14

Você precisa de um tratamento de erro um pouco diferente para as funções do PowerShell e para chamar exe, além de informar ao chamador do seu script que ele falhou. Construindo sobre Execa biblioteca Psake, um script que tem a estrutura abaixo irá parar com todos os erros e é utilizável como modelo base para a maioria dos 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
fonte
Invocando por exemplo: Exec { sqlite3.exe -bail some.db "$SQL" }a -bailcausa um erro, uma vez que está tentando interpretá-lo como um parâmetro Cmdlet? O agrupamento de aspas não parece funcionar. Alguma ideia?
rcoup 25/03
Existe uma maneira de colocar esse código em algum lugar central para que você possa fazer algum tipo de # inclusão quando quiser usar o método Exec?
NickG
10

Uma pequena modificação na resposta 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

As principais diferenças aqui são:

  1. ele usa o verbo-substantivo (imitando Invoke-Command)
  2. implica que ele usa o operador de chamada sob as cobertas
  3. imita o -ErrorAction comportamento dos cmdlets internos
  4. sai com o mesmo código de saída em vez de lançar uma exceção com a nova mensagem
Lucas
fonte
1
Como você passa parâmetros / variáveis? por exemploInvoke-Call { dotnet build $something }
Michael Blake
1
@MichaelBlake, a sua pergunta é tão certa, permitir que um repasse de parâmetros tornaria essa abordagem dourada. Estou inspecionando adamtheautomator.com/pass-hashtables-invoke-command-argument para ajustar a chamada de chamada para oferecer suporte à passagem de parâmetros. Se eu conseguir, vou postá-lo como outra resposta aqui.
Maxim V. Pavlov
Por que você usa o operador de splatting durante a chamada? O que isso te leva? & @ScriptBlocke & $ScriptBlockparece fazer a mesma coisa. Não têm sido capazes de google qual é a diferença neste caso
pinkfloydx33
6

Eu sou novo no PowerShell, mas isso parece ser mais eficaz:

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

Eu vim aqui procurando a mesma coisa. $ ErrorActionPreference = "Stop" mata meu shell imediatamente quando eu preferir ver a mensagem de erro (pausa) antes de terminar. Recorrendo às minhas sensibilidades de lote:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Descobri que isso funciona da mesma forma para o meu script ps1 específico:

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

Redirecionar stderrpara stdoutparece também fazer o truque sem quaisquer outros comandos / wrappers de scriptblock, embora eu não consiga encontrar uma explicação por que funciona dessa maneira.

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Isso produzirá o seguinte conforme o esperado (o comando aws s3 ...retorna $LASTEXITCODE = 255)

PS> .\test.ps1

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