Bagaimana cara menghentikan skrip PowerShell pada kesalahan pertama?

250

Saya ingin skrip PowerShell saya berhenti ketika salah satu perintah yang saya jalankan gagal (seperti set -edalam bash). Saya menggunakan perintah Powershell ( New-Object System.Net.WebClient) dan program ( .\setup.exe).

Andres Riofrio
sumber

Jawaban:

303

$ErrorActionPreference = "Stop" akan membuat Anda menjadi bagian dari perjalanan ke sana (yaitu ini berfungsi bagus untuk cmdlets).

Namun untuk EXE Anda harus memeriksa $LastExitCodediri Anda sendiri setelah setiap pemanggilan exe dan menentukan apakah itu gagal atau tidak. Sayangnya saya tidak berpikir PowerShell dapat membantu di sini karena pada Windows, EXEs tidak terlalu konsisten pada apa yang merupakan kode keluar "sukses" atau "kegagalan". Sebagian besar mengikuti standar UNIX 0 yang menunjukkan keberhasilan tetapi tidak semua melakukannya. Lihat fungsi CheckLastExitCode di posting blog ini . Anda mungkin menemukan itu berguna.

Keith Hill
sumber
3
Apakah $ErrorActionPreference = "Stop"bekerja untuk program yang berperilaku baik (yang mengembalikan 0 pada keberhasilan)?
Andres Riofrio
14
Tidak, tidak berfungsi sama sekali untuk EXE. Ini hanya berfungsi untuk cmdlet PowerShell yang berjalan dalam proses. Agak menyakitkan, tetapi Anda harus memeriksa $ LastExitCode setelah setiap pemanggilan EXE, memeriksa terhadap kode keluar yang diharapkan dan jika tes itu menunjukkan kegagalan, Anda harus membuang untuk menghentikan eksekusi skrip misalnya di throw "$exe failed with exit code $LastExitCode"mana $ exe hanyalah jalan menuju EXE.
Keith Hill
2
Diterima karena termasuk info tentang cara membuatnya bekerja dengan program eksternal.
Andres Riofrio
4
Saya memasukkan permintaan fitur tentang hal ini di sini: connect.microsoft.com/PowerShell/feedback/details/751703/…
Helephant
5
perhatikan bahwa psake memiliki commandlet yang disebut "exec" yang dapat Anda gunakan untuk membungkus panggilan ke program eksternal dengan tanda centang untuk LastExitCode dan menampilkan kesalahan (dan berhenti, jika diinginkan)
enorl76
68

Anda harus dapat mencapai ini dengan menggunakan pernyataan $ErrorActionPreference = "Stop"di awal skrip Anda.

Pengaturan defaultnya $ErrorActionPreferenceadalah Continue, itulah sebabnya Anda melihat skrip Anda terus terjadi setelah kesalahan terjadi.

goric
sumber
21
Ini tidak mempengaruhi program, hanya cmdlet.
Joey
18

Sayangnya, karena cmdlets bermasalah seperti New-RegKey dan Clear-Disk , tidak ada jawaban yang cukup. Saat ini saya telah menetapkan baris berikut di bagian atas skrip PowerShell untuk menjaga kewarasan saya.

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

dan lalu panggilan asli apa pun yang mendapatkan perawatan ini:

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

Pola panggilan asli itu perlahan-lahan menjadi cukup umum bagi saya sehingga saya mungkin harus mencari opsi untuk membuatnya lebih ringkas. Saya masih seorang pemula yang tangguh, jadi saran dipersilahkan.

agieNick02
sumber
14

Anda perlu penanganan kesalahan yang sedikit berbeda untuk fungsi PowerShell dan untuk memanggil exe, dan Anda harus memastikan untuk memberi tahu penelepon skrip Anda bahwa ia telah gagal. Membangun di atas dari Execperpustakaan Psake, skrip yang memiliki struktur di bawah ini akan berhenti pada semua kesalahan, dan dapat digunakan sebagai templat dasar untuk sebagian besar skrip.

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
sumber
Meminjam misalnya: Exec { sqlite3.exe -bail some.db "$SQL" }, yang -bailmenyebabkan kesalahan karena mencoba untuk menafsirkannya sebagai parameter Cmdlet? Membungkus barang dengan tanda kutip sepertinya tidak berhasil. Ada ide?
rcoup
Apakah ada cara untuk meletakkan kode ini di suatu tempat di tengah sehingga Anda dapat melakukan semacam #include ketika Anda ingin menggunakan metode Exec?
NickG
10

Sedikit modifikasi pada jawaban dari @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

Perbedaan utama di sini adalah:

  1. menggunakan Verb-Noun (mimicing Invoke-Command)
  2. menyiratkan bahwa ia menggunakan operator panggilan di bawah selimut
  3. meniru -ErrorAction perilaku dari built in cmdlets
  4. keluar dengan kode keluar yang sama daripada melemparkan pengecualian dengan pesan baru
Lucas
sumber
1
Bagaimana Anda melewatkan parameter / variabel? mis.Invoke-Call { dotnet build $something }
Michael Blake
1
@MichaelBlake pertanyaan Anda sangat benar, membiarkan params passthrough akan membuat pendekatan ini menjadi emas. Saya sedang memeriksa adamtheautomator.com/pass-hashtables-invoke-command-argument untuk menyesuaikan Invoke-Call untuk mendukung params pass-through. Jika saya berhasil, saya akan mempostingnya sebagai jawaban lain di sini.
Maxim V. Pavlov
Mengapa Anda menggunakan operator splatting selama pemanggilan? Apa yang Anda dapatkan? & @ScriptBlockdan & $ScriptBlocktampaknya melakukan hal yang sama. Belum dapat Google perbedaan apa dalam kasus ini
pinkfloydx33
6

Saya baru mengenal PowerShell tetapi ini tampaknya paling efektif:

doSomething -arg myArg
if (-not $?) {throw "Failed to doSomething"}
Peter L.
sumber
2

Saya datang ke sini mencari hal yang sama. $ ErrorActionPreference = "Stop" membunuh shell saya segera ketika saya lebih suka melihat pesan kesalahan (jeda) sebelum itu berakhir. Mengalami kepekaan batch saya:

IF %ERRORLEVEL% NEQ 0 pause & GOTO EOF

Saya menemukan bahwa ini berfungsi hampir sama untuk skrip ps1 khusus saya:

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

Mengarahkan kembali stderrke stdouttampaknya juga melakukan trik tanpa pembungkus perintah / script lainnya meskipun saya tidak dapat menemukan penjelasan mengapa itu bekerja seperti itu ..

# test.ps1

$ErrorActionPreference = "Stop"

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

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

Ini akan menampilkan yang berikut seperti yang diharapkan (perintah aws s3 ...kembali $LASTEXITCODE = 255)

PS> .\test.ps1

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
==> pass
ubi
sumber