จะหยุดสคริปต์ PowerShell ในข้อผิดพลาดแรกได้อย่างไร


250

ฉันต้องการให้สคริปต์ PowerShell ของฉันหยุดทำงานเมื่อคำสั่งใด ๆ ที่ฉันเรียกใช้ล้มเหลว (เช่นset -eในทุบตี) ฉันใช้ทั้งคำสั่ง Powershell ( New-Object System.Net.WebClient) และโปรแกรม ( .\setup.exe)

คำตอบ:


303

$ErrorActionPreference = "Stop" คุณจะได้เป็นส่วนหนึ่งของวิธีการที่นั่น (เช่นนี้ทำงานได้ดีสำหรับ cmdlets)

อย่างไรก็ตามสำหรับ EXE คุณจะต้องตรวจสอบ$LastExitCodeตัวเองทุกครั้งหลังจากการเรียกใช้ exe และตรวจสอบว่าล้มเหลวหรือไม่ น่าเสียดายที่ฉันไม่คิดว่า PowerShell สามารถช่วยได้ที่นี่เพราะใน Windows EXE ไม่สอดคล้องกันอย่างมากกับสิ่งที่ถือเป็นรหัสออก "สำเร็จ" หรือ "ล้มเหลว" ส่วนใหญ่ปฏิบัติตามมาตรฐาน UNIX ที่ระบุถึงความสำเร็จ แต่ไม่ใช่ทั้งหมดที่ทำได้ ลองใช้ฟังก์ชั่น CheckLastExitCode ในบล็อกโพสต์นี้ คุณอาจพบว่ามันมีประโยชน์.


3
ใช้$ErrorActionPreference = "Stop"งานได้กับโปรแกรมที่ทำงานได้ดี (ที่ส่งคืน 0 เมื่อสำเร็จ)
Andres Riofrio

14
ไม่มันไม่ทำงานเลยสำหรับ EXE ใช้งานได้เฉพาะกับ PowerShell cmdlets ซึ่งทำงานอยู่ระหว่างดำเนินการ มันเป็นความเจ็บปวด แต่คุณต้องตรวจสอบ $ LastExitCode ทุกครั้งที่มีการร้องขอ EXE ตรวจสอบกับรหัสทางออกที่คาดไว้และหากการทดสอบนั้นบ่งชี้ว่าล้มเหลวคุณต้องโยนเพื่อยุติการดำเนินการของสคริปต์เช่นthrow "$exe failed with exit code $LastExitCode"ที่ $ exe EXE
Keith Hill

2
ยอมรับเนื่องจากมีข้อมูลเกี่ยวกับวิธีทำให้มันทำงานกับโปรแกรมภายนอก
Andres Riofrio

4
ฉันใส่คำขอคุณลักษณะไว้ที่นี่: connect.microsoft.com/PowerShell/feedback/details/751703/…
ช่วย

5
โปรดทราบว่า psake มี commandlet ชื่อ "exec" ซึ่งคุณสามารถใช้เพื่อตัดการเรียกไปยังโปรแกรมภายนอกด้วยการตรวจสอบ LastExitCode และแสดงข้อผิดพลาด (และหยุดถ้าต้องการ)
enorl76

68

คุณควรจะสามารถทำได้โดยใช้คำสั่ง$ErrorActionPreference = "Stop"ที่จุดเริ่มต้นของสคริปต์ของคุณ

การตั้งค่าเริ่มต้น$ErrorActionPreferenceคือContinueซึ่งเป็นสาเหตุที่คุณเห็นสคริปต์ของคุณดำเนินต่อไปหลังจากเกิดข้อผิดพลาด


21
สิ่งนี้ไม่มีผลกับโปรแกรมเฉพาะ cmdlets
Joey

18

น่าเศร้าเนื่องจาก cmdlet buggy เช่น 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'
}

รูปแบบการโทรแบบดั้งเดิมนั้นค่อย ๆ กลายเป็นเรื่องธรรมดาพอสำหรับฉันที่ฉันอาจจะต้องดูตัวเลือกต่าง ๆ เพื่อทำให้รัดกุมยิ่งขึ้น ฉันยังคงเป็นมือใหม่ที่มีพลังดังนั้นข้อเสนอแนะยินดีต้อนรับ


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
}

การกล่าวอ้างเช่นExec { sqlite3.exe -bail some.db "$SQL" }นี้-bailทำให้เกิดข้อผิดพลาดเพราะมันพยายามที่จะตีความมันเป็นพารามิเตอร์ Cmdlet? การห่อสิ่งต่าง ๆ ด้วยคำพูดดูเหมือนจะไม่ทำงาน ความคิดใด ๆ
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พฤติกรรมเลียนแบบจากการสร้างใน cmdlets
  4. ออกด้วยรหัสออกเดียวกันแทนที่จะทิ้งข้อยกเว้นด้วยข้อความใหม่

1
คุณจะผ่านพารามิเตอร์ / ตัวแปรได้อย่างไร เช่นInvoke-Call { dotnet build $something }
Michael Blake

1
@MichaelBlake สอบถามรายละเอียดเพิ่มเติมของคุณถูกต้องการอนุญาตให้ใช้ paras passthrough จะทำให้วิธีการนี้เป็นทองคำ ฉันกำลังตรวจสอบadamtheautomator.com/pass-hashtables-invoke-command-argumentเพื่อปรับ Invoke-Call เพื่อรองรับ params pass-through หากฉันประสบความสำเร็จฉันจะโพสต์เป็นคำตอบอื่นที่นี่
Maxim V. Pavlov

ทำไมคุณถึงใช้ตัวดำเนินการการกระจายระหว่างการเรียกใช้ อะไรที่ทำให้คุณได้รับ & @ScriptBlockและ& $ScriptBlockดูเหมือนจะทำสิ่งเดียวกัน ยังไม่สามารถ google สิ่งที่แตกต่างในกรณีนี้
pinkfloydx33

6

ฉันใหม่กับ powershell แต่ดูเหมือนว่าจะมีประสิทธิภาพมากที่สุด:

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

2

ฉันมาที่นี่เพื่อค้นหาสิ่งเดียวกัน $ ErrorActionPreference = "หยุด" ฆ่าเปลือกของฉันทันทีเมื่อฉันอยากเห็นข้อความแสดงข้อผิดพลาด (หยุดชั่วคราว) ก่อนที่มันจะจบลง ย้อนกลับไปที่ความอ่อนไหวแบบกลุ่มของฉัน:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

ฉันพบว่ามันใช้งานได้ดีเหมือนกันกับสคริปต์ ps1 ของฉัน:

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

0

เปลี่ยนเส้นทางstderrไปยังstdoutดูเหมือนว่าจะยังทำเคล็ดลับโดยไม่ต้องคำสั่งอื่น ๆ / ห่อ scriptblock ใด ๆ แม้ว่าฉันไม่สามารถหาคำอธิบายว่าทำไมมันทำงานแบบว่า ..

# 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.