Come interrompere uno script PowerShell al primo errore?


250

Voglio che il mio script PowerShell si fermi quando uno qualsiasi dei comandi che eseguo fallisce (come set -ein bash). Sto usando sia i comandi Powershell ( New-Object System.Net.WebClient) che i programmi ( .\setup.exe).

Risposte:


304

$ErrorActionPreference = "Stop" ti farà parte del percorso (cioè funziona benissimo per i cmdlet).

Tuttavia, per gli EXE dovrai controllare $LastExitCodete stesso dopo ogni chiamata exe e determinare se fallisce o no. Sfortunatamente non credo che PowerShell possa essere d'aiuto in questo caso perché su Windows gli EXE non sono terribilmente coerenti su ciò che costituisce un codice di uscita "successo" o "errore". La maggior parte segue lo standard UNIX di 0 che indica il successo, ma non tutti lo fanno. Scopri la funzione CheckLastExitCode in questo post del blog . Potresti trovarlo utile.


3
Fa $ErrorActionPreference = "Stop"il lavoro per i programmi ben educati (che il ritorno 0 in caso di successo)?
Andres Riofrio,

14
No, non funziona affatto per gli EXE. Funziona solo con i cmdlet di PowerShell eseguiti in-process. È un po 'doloroso, ma devi controllare $ LastExitCode dopo ogni richiamo EXE, verificarlo rispetto al codice di uscita previsto e se quel test indica un errore, devi lanciare per terminare l'esecuzione dello script, ad esempio throw "$exe failed with exit code $LastExitCode"dove $ exe è solo il percorso per il EXE.
Keith Hill,

2
Accettato perché include informazioni su come farlo funzionare con programmi esterni.
Andres Riofrio,

4
Ho inserito una richiesta di funzionalità al riguardo qui: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant

5
nota che psake ha un commandlet chiamato "exec" che puoi usare per avvolgere le chiamate a programmi esterni con un controllo per LastExitCode e visualizzare un errore (e fermare, se lo desideri)
enorl76

68

Dovresti essere in grado di farlo utilizzando la dichiarazione $ErrorActionPreference = "Stop"all'inizio dei tuoi script.

L'impostazione predefinita $ErrorActionPreferenceè è Continue, motivo per cui si vedono gli script continuare dopo che si sono verificati errori.


21
Ciò non influisce sui programmi, ma solo sui cmdlet.
Joey,

19

Purtroppo, a causa di cmdlet con errori come New-RegKey e Clear-Disk , nessuna di queste risposte è sufficiente. Attualmente ho optato per le seguenti righe nella parte superiore di qualsiasi script PowerShell per mantenere la mia sanità mentale.

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

e quindi qualsiasi chiamata nativa ottiene questo trattamento:

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

Quel modello di chiamata nativo sta lentamente diventando abbastanza comune per me che dovrei probabilmente esaminare le opzioni per renderlo più conciso. Sono ancora un principiante di PowerShell, quindi i suggerimenti sono benvenuti.


Questo funziona - grazie!
Un fratello

14

Hai bisogno di una gestione degli errori leggermente diversa per le funzioni di PowerShell e per la chiamata di exe, e devi essere sicuro di dire al chiamante del tuo script che non è riuscito. Costruito sopra Execdalla libreria Psake, uno script che ha la struttura seguente si fermerà su tutti gli errori ed è utilizzabile come modello di base per la maggior parte degli script.

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
}

Invocando per esempio: Exec { sqlite3.exe -bail some.db "$SQL" }la -bailcausa un errore in quanto si sta cercando di interpretarlo come parametro cmdlet? Avvolgere le cose tra virgolette non sembra funzionare. Qualche idea?
Recupera il

C'è un modo per mettere questo codice in qualche luogo centrale in modo da poter fare una sorta di #include quando si desidera utilizzare il metodo Exec?
NickG

10

Una leggera modifica alla risposta di @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

Le differenze chiave qui sono:

  1. usa il Verbo-Noun (imitando Invoke-Command)
  2. implica che utilizza l' operatore di chiamata sotto le copertine
  3. imita il -ErrorAction comportamento dai cmdlet integrati
  4. esce con lo stesso codice di uscita anziché generare un'eccezione con un nuovo messaggio

1
Come si passano parametri / variabili? ad es.Invoke-Call { dotnet build $something }
Michael Blake,

1
@MichaelBlake la tua inchiesta è così giusta, consentire un passaggio params renderebbe questo approccio oro. Sto ispezionando adamtheautomator.com/pass-hashtables-invoke-command-argument per adattare Invoke-Call per supportare il pass-through dei parametri. Se avrò successo, la posterò come un'altra risposta qui.
Maxim V. Pavlov,

Perché usi l'operatore splatting durante il richiamo? Cosa ti prende? & @ScriptBlocke & $ScriptBlocksembrano fare la stessa cosa. Non sono stato in grado di cercare su Google la differenza in questo caso
pinkfloydx33,

6

Sono nuovo di PowerShell ma questo sembra essere più efficace:

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

2

Sono venuto qui alla ricerca della stessa cosa. $ ErrorActionPreference = "Stop" uccide immediatamente la mia shell quando preferisco vedere il messaggio di errore (pausa) prima che termini. Ripiegando sulla mia sensibilità batch:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Ho scoperto che questo funziona praticamente allo stesso modo per il mio particolare script ps1:

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

0

Reindirizzamento stderral stdoutsembra fare anche il trucco, senza altri comandi / involucri scriptblock anche se non riesco a trovare una spiegazione perché funziona in questo modo ..

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Questo produrrà quanto segue come previsto (il comando aws s3 ...restituisce $LASTEXITCODE = 255)

PS> .\test.ps1

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.