Làm cách nào để dừng tập lệnh PowerShell ở lỗi đầu tiên?


250

Tôi muốn tập lệnh PowerShell của tôi dừng khi bất kỳ lệnh nào tôi chạy không thành công (như set -e trong bash). Tôi đang sử dụng cả hai lệnh Powershell ( New-Object System.Net.WebClient) và chương trình ( .\setup.exe).

Câu trả lời:


303

$ErrorActionPreference = "Stop" sẽ giúp bạn trở thành một phần của cách đó (nghĩa là điều này hoạt động rất tốt cho các lệnh ghép ngắn).

Tuy nhiên, đối với EXE, bạn sẽ cần tự kiểm tra $LastExitCodesau mỗi lần gọi exe và xác định xem điều đó có thất bại hay không. Thật không may, tôi không nghĩ PowerShell có thể giúp đỡ ở đây vì trên Windows, EXE không nhất quán khủng khiếp về những gì cấu thành mã thoát "thành công" hay "thất bại". Hầu hết tuân theo tiêu chuẩn UNIX là 0 chỉ ra thành công nhưng không phải tất cả đều làm được. Kiểm tra chức năng CheckLastExitCode trong bài đăng trên blog này . Bạn có thể thấy nó hữu ích.


3
$ErrorActionPreference = "Stop"hoạt động cho các chương trình hoạt động tốt (trả về 0 khi thành công) không?
Andres Riofrio

14
Không, nó hoàn toàn không hoạt động đối với EXE. Nó chỉ hoạt động cho các lệnh ghép ngắn PowerShell chạy trong tiến trình. Thật là khó khăn nhưng bạn phải kiểm tra $ LastExitCode sau mỗi lần gọi EXE, kiểm tra xem mã thoát dự kiến ​​và nếu kiểm tra đó cho thấy thất bại, bạn phải ném để chấm dứt thực thi tập lệnh, ví dụ như throw "$exe failed with exit code $LastExitCode"$ exe chỉ là đường dẫn đến EXE.
Keith Hill

2
Được chấp nhận vì nó bao gồm thông tin về cách làm cho nó hoạt động với các chương trình bên ngoài.
Andres Riofrio

4
Tôi đặt trong một yêu cầu tính năng về nó ở đây: connect.microsoft.com/PowerShell/feedback/details/751703/...
Helephant

5
lưu ý rằng psake có một lệnh được gọi là "exec" mà bạn có thể sử dụng để ngắt các cuộc gọi đến các chương trình bên ngoài bằng một kiểm tra LastExitCode và hiển thị một lỗi (và dừng lại, nếu muốn)
enorl76

68

Bạn sẽ có thể thực hiện điều này bằng cách sử dụng câu lệnh $ErrorActionPreference = "Stop"ở đầu tập lệnh của bạn.

Cài đặt mặc định $ErrorActionPreferenceContinue, đó là lý do tại sao bạn thấy các tập lệnh của mình tiếp tục xảy ra sau khi xảy ra lỗi.


21
Điều này không ảnh hưởng đến các chương trình, chỉ các lệnh ghép ngắn.
Joey

18

Đáng buồn thay, do các lệnh ghép ngắn lỗi như New-RegKey và Clear-Disk , không có câu trả lời nào trong số này là đủ. Hiện tại tôi đã giải quyết các dòng sau ở đầu bất kỳ kịch bản quyền hạn nào để duy trì sự tỉnh táo của mình.

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

và sau đó bất kỳ cuộc gọi bản địa nào cũng được điều trị này:

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

Mô hình cuộc gọi bản địa đó đang dần trở nên đủ phổ biến đối với tôi mà có lẽ tôi nên xem xét các lựa chọn để làm cho nó ngắn gọn hơn. Tôi vẫn là một người mới sử dụng năng lực, vì vậy các đề xuất đều được chào đón.


14

Bạn cần xử lý lỗi hơi khác nhau đối với các hàm powershell và để gọi exe, và bạn cần chắc chắn nói với người gọi về tập lệnh của mình rằng nó đã bị lỗi. Xây dựng Exectừ thư viện Psake, một tập lệnh có cấu trúc bên dưới sẽ dừng trên tất cả các lỗi và có thể sử dụng làm mẫu cơ sở cho hầu hết các tập lệnh.

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
}

Gọi ví dụ : Exec { sqlite3.exe -bail some.db "$SQL" }, -bailnguyên nhân gây ra lỗi vì nó cố gắng diễn giải nó như một tham số Cmdlet? Gói mọi thứ trong dấu ngoặc kép dường như không hoạt động. Có ý kiến ​​gì không?
RCoup

Có cách nào để đặt mã này ở một nơi trung tâm để bạn có thể thực hiện một số loại #incoide khi bạn muốn sử dụng phương thức Exec không?
NickG

10

Một sửa đổi nhỏ cho câu trả lời từ @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

Sự khác biệt chính ở đây là:

  1. nó sử dụng Danh từ động từ (bắt chước Invoke-Command)
  2. ngụ ý rằng nó sử dụng toán tử cuộc gọi dưới vỏ bọc
  3. -ErrorActionhành vi bắt chước từ được xây dựng trong cmdlets
  4. thoát với cùng một mã thoát thay vì ném ngoại lệ với tin nhắn mới

1
Làm thế nào để bạn vượt qua các tham số / biến? ví dụInvoke-Call { dotnet build $something }
Michael Blake

1
@MichaelBlake yêu cầu của bạn là rất đúng, cho phép một thông số thông qua sẽ làm cho phương pháp này trở nên vàng. Tôi đang kiểm tra adamtheautomator.com/pass-hashtables-invoke-command-argument để điều chỉnh Gọi Gọi để hỗ trợ thông qua thông số. Nếu tôi thành công, tôi sẽ đăng nó như một câu trả lời khác ở đây.
Maxim V. Pavlov

Tại sao bạn sử dụng toán tử ghép nối trong khi gọi? Điều đó có được bạn? & @ScriptBlock& $ScriptBlockxuất hiện để làm điều tương tự. Không thể google được sự khác biệt trong trường hợp này
Pinkfloydx33

6

Tôi mới sử dụng powershell nhưng điều này có vẻ hiệu quả nhất:

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

2

Tôi đến đây để tìm kiếm điều tương tự. $ ErrorActionPreference = "Stop" giết chết shell của tôi ngay lập tức khi tôi muốn xem thông báo lỗi (tạm dừng) trước khi nó kết thúc. Trở lại với sự nhạy cảm hàng loạt của tôi:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Tôi thấy rằng điều này hoạt động khá giống với kịch bản ps1 cụ thể của tôi:

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

0

Chuyển hướng stderrtới stdoutdường như cũng làm các trick mà không cần bất kỳ lệnh / wrappers scriptblock khác mặc dù tôi không thể tìm thấy một lời giải thích lý do tại sao nó hoạt động như vậy ..

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Điều này sẽ xuất ra như sau như mong đợi (lệnh aws s3 ...trả về $LASTEXITCODE = 255)

PS> .\test.ps1

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.