Jak zatrzymać skrypt PowerShell przy pierwszym błędzie?

250

Chcę, aby mój skrypt PowerShell przestał działać, gdy którekolwiek z poleceń, które wykonuję, zawiodły (jak set -ew bash). Używam zarówno poleceń Powershell ( New-Object System.Net.WebClient), jak i programów ( .\setup.exe).

Andres Riofrio
źródło

Odpowiedzi:

303

$ErrorActionPreference = "Stop" dostarczy ci część drogi (to działa świetnie dla poleceń cmdlet).

Jednak w przypadku plików EXE będziesz musiał sprawdzić $LastExitCodesię po każdym wywołaniu exe i ustalić, czy nie powiodło się. Niestety nie sądzę, aby PowerShell mógł tutaj pomóc, ponieważ w systemie Windows pliki EXE nie są strasznie spójne z kodem wyjścia „sukcesu” lub „niepowodzenia”. Większość z nich przestrzega standardu UNIX 0, co oznacza sukces, ale nie wszystkie. Sprawdź funkcję CheckLastExitCode w tym poście na blogu . Może ci się przydać.

Keith Hill
źródło
3
Czy $ErrorActionPreference = "Stop"działa dla dobrze wychowanych programów (które zwracają 0 po sukcesie)?
Andres Riofrio
14
Nie, w ogóle nie działa dla plików EXE. Działa tylko w przypadku poleceń cmdlet programu PowerShell, które działają w trakcie przetwarzania. Jest to trochę bolesne, ale musisz sprawdzić $ LastExitCode po każdym wywołaniu EXE, sprawdzić, czy w porównaniu z oczekiwanym kodem wyjścia i jeśli ten test wskazuje na niepowodzenie, musisz rzucić, aby zakończyć wykonywanie skryptu, np. throw "$exe failed with exit code $LastExitCode"Gdzie $ exe jest tylko ścieżką do EXE.
Keith Hill
2
Zaakceptowano, ponieważ zawiera informacje o tym, jak sprawić, by działał z programami zewnętrznymi.
Andres Riofrio,
4
Tutaj zamieściłem prośbę o tę funkcję: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant
5
zwróć uwagę, że psake ma komendę o nazwie „exec”, której można użyć do zawijania wywołań programów zewnętrznych za pomocą funkcji LastExitCode i wyświetlania błędu (i zatrzymania, jeśli to konieczne)
enorl76,
68

Powinieneś być w stanie to osiągnąć za pomocą instrukcji $ErrorActionPreference = "Stop"na początku skryptu.

Domyślne ustawienie $ErrorActionPreferenceto Continue, dlatego skrypty nadal działają po wystąpieniu błędów.

goric
źródło
21
Nie wpływa to na programy, tylko na polecenia cmdlet.
Joey
18

Niestety, z powodu błędnych poleceń cmdlet, takich jak New-RegKey i Clear-Disk , żadna z tych odpowiedzi nie jest wystarczająca. W tej chwili zdecydowałem się na następujące wiersze u góry dowolnego skryptu PowerShell, aby zachować zdrowie psychiczne.

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

a następnie każda rozmowa natywna otrzymuje takie leczenie:

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

Ten natywny wzorzec połączeń powoli staje się dla mnie na tyle powszechny, że prawdopodobnie powinienem rozważyć opcje jego zwięzłości. Nadal jestem nowicjuszem PowerShell, więc sugestie są mile widziane.

aggieNick02
źródło
14

Potrzebujesz nieco innej obsługi błędów dla funkcji PowerShell i wywoływania exe, i musisz koniecznie powiedzieć wywołującemu skryptowi, że się nie udało. Opierając się na Execbibliotece Psake, skrypt o poniższej strukturze zatrzyma się na wszystkich błędach i jest użyteczny jako podstawowy szablon dla większości skryptów.

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
źródło
Powołując się na przykład: Exec { sqlite3.exe -bail some.db "$SQL" }The -bailpowoduje błąd, ponieważ stara się interpretować go jako parametr apletu polecenia? Zawijanie rzeczy w cytaty wydaje się nie działać. Jakieś pomysły?
rcoup
Czy istnieje sposób na umieszczenie tego kodu gdzieś w centrum, abyś mógł wykonać #include, jeśli chcesz użyć metody Exec?
NickG
10

Drobna modyfikacja odpowiedzi z @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

Kluczowe różnice tutaj:

  1. używa czasownika-rzeczownika (naśladowanie Invoke-Command)
  2. oznacza, że ​​korzysta z operatora połączenia pod osłonami
  3. naśladuje -ErrorAction zachowanie z wbudowanych poleceń cmdlet
  4. wychodzi z tym samym kodem wyjścia, a nie zgłasza wyjątek z nową wiadomością
Lucas
źródło
1
Jak przekazujesz parametry / zmienne? np.Invoke-Call { dotnet build $something }
Michael Blake
1
@MichaelBlake twoje zapytanie jest tak słuszne, pozwolenie na przejście params sprawiłoby, że to podejście byłoby złote. Mam kontroli adamtheautomator.com/pass-hashtables-invoke-command-argument dostosować się do wspierania params pass-through z Invoke-Call. Jeśli mi się uda, opublikuję to jako kolejną odpowiedź tutaj.
Maxim V. Pavlov,
Dlaczego używasz operatora rozpryskiwania podczas wywołania? Co ci to daje? & @ScriptBlocki & $ScriptBlockwydaje się, że robią to samo. Nie mogłem google, jaka jest różnica w tym przypadku
pinkfloydx33
6

Jestem nowy w PowerShell, ale wydaje się to najbardziej skuteczne:

doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
Peter L.
źródło
2

Przybyłem tutaj, szukając tego samego. $ ErrorActionPreference = „Stop” zabija moją powłokę natychmiast, gdy wolę zobaczyć komunikat o błędzie (pauza) przed jego zakończeniem. Opierając się na mojej wrażliwości partii:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Przekonałem się, że działa to tak samo dla mojego konkretnego skryptu ps1:

Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}
harvey263
źródło
0

Wydaje stderrsię, stdoutże przekierowanie do robi to samo bez żadnych innych poleceń / bloków skryptów, chociaż nie mogę znaleźć wyjaśnienia, dlaczego tak działa ...

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Spowoduje to wyświetlenie następujących danych zgodnie z oczekiwaniami (polecenie aws s3 ...zwraca $LASTEXITCODE = 255)

PS> .\test.ps1

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass
ubi
źródło