Как остановить скрипт PowerShell при первой ошибке?

250

Я хочу, чтобы мой скрипт PowerShell останавливался, когда любая из команд, которые я запускаю, не срабатывает (как set -eв bash). Я использую обе команды Powershell ( New-Object System.Net.WebClient) и программ ( .\setup.exe).

Андрес Риофрио
источник

Ответы:

304

$ErrorActionPreference = "Stop" поможет вам в этом (например, это прекрасно работает для командлетов).

Однако для EXE вы должны будете проверять $LastExitCodeсебя после каждого вызова exe и определять, произошел ли сбой или нет. К сожалению, я не думаю, что PowerShell может помочь в этом, потому что в Windows EXE-файлы не очень согласуются с тем, что представляет собой код завершения «успех» или «отказ». Большинство из них следуют стандарту UNIX 0, что указывает на успех, но не все. Проверьте функцию CheckLastExitCode в этом сообщении в блоге . Вы можете найти это полезным.

Кит Хилл
источник
3
Работает ли $ErrorActionPreference = "Stop"для хороших программ (которые возвращают 0 в случае успеха)?
Андрес Риофрио
14
Нет, это не работает для EXE-файлов. Это работает только для командлетов PowerShell, которые выполняются в процессе. Это немного болезненно, но вы должны проверять $ LastExitCode после каждого вызова EXE, проверять его на соответствие ожидаемому коду завершения и, если этот тест указывает на ошибку, вы должны бросить, чтобы завершить выполнение скрипта, например, throw "$exe failed with exit code $LastExitCode"где $ exe - просто путь к EXE.
Кит Хилл
2
Принимается, поскольку содержит информацию о том, как заставить его работать с внешними программами.
Андрес Риофрио
4
Я вставил запрос об этом здесь: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant
5
обратите внимание, что в psake есть командлет с именем «exec», который вы можете использовать для переноса вызовов во внешние программы с проверкой LastExitCode и отображения ошибки (и остановки при желании)
enorl76
68

Вы должны быть в состоянии сделать это, используя оператор $ErrorActionPreference = "Stop"в начале ваших сценариев.

Значение по умолчанию $ErrorActionPreferenceIS Continue, поэтому вы видите ваши сценарии продолжает идти после возникновения ошибок.

goric
источник
21
Это не влияет на программы, только командлеты.
Джои
20

К сожалению, из-за ошибочных командлетов, таких как New-RegKey и Clear-Disk , ни одного из этих ответов недостаточно. В настоящее время я остановился на следующих строках в верхней части любого сценария powershell, чтобы сохранить здравомыслие.

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

и тогда любой родной звонок получает это лечение:

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

Этот собственный шаблон вызовов постепенно становится достаточно распространенным для меня, поэтому я, вероятно, должен рассмотреть варианты, чтобы сделать его более кратким. Я все еще новичок в PowerShell, поэтому предложения приветствуются.

aggieNick02
источник
Это работает - спасибо!
BR
14

Вам нужно немного по-другому обрабатывать ошибки для функций powershell и для вызова exe-файлов, и вы должны быть уверены, что сообщили вызывающему скрипту, что он потерпел неудачу. Построенный поверх Execбиблиотеки Psake, скрипт со структурой ниже остановит все ошибки и будет использоваться в качестве базового шаблона для большинства скриптов.

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
источник
Призывая например Exec { sqlite3.exe -bail some.db "$SQL" }, то -bailвызывает ошибку , так как он пытается интерпретировать его в качестве параметра командлета? Упаковка вещей в кавычки, похоже, не работает. Любые идеи?
rcoup
Есть ли способ поместить этот код где-то в центре, чтобы вы могли сделать что-то вроде #include, когда вы хотите использовать метод Exec?
NickG
10

Небольшая модификация ответа от @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

Ключевые отличия здесь:

  1. он использует глагол-существительное (подражая Invoke-Command)
  2. подразумевает, что он использует оператор вызова под прикрытием
  3. имитирует -ErrorAction поведение встроенных командлетов
  4. завершает работу с тем же кодом завершения, а не создает исключение с новым сообщением
Лукас
источник
1
Как вы передаете параметры / переменные? напримерInvoke-Call { dotnet build $something }
Майкл Блейк
1
@MichaelBlake Ваш запрос настолько прав, что разрешение на передачу параметров сделало бы этот подход золотым. Я проверяю adamtheautomator.com/pass-hashtables-invoke-command-argument, чтобы настроить Invoke-Call для поддержки передачи параметров. Если мне это удастся, я опубликую это как еще один ответ здесь.
Максим Павлов
Почему вы используете оператор splatting во время вызова? Что это тебе дает? & @ScriptBlockи, & $ScriptBlockкажется, делают то же самое. Не удалось гуглить, какая разница в этом случае
pinkfloydx33
6

Я новичок в powershell, но это кажется наиболее эффективным:

doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
Питер Л
источник
2

Я пришел сюда в поисках того же. $ ErrorActionPreference = "Stop" немедленно убивает мою оболочку, когда я предпочитаю увидеть сообщение об ошибке (пауза) до его завершения. Возвращаясь к моей партии чувств:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Я обнаружил, что это работает примерно так же для моего конкретного сценария PS1:

Import-PSSession $Session
If ($? -ne "True") {Pause; Exit}
harvey263
источник
0

Перенаправление 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
уби
источник