- 05/23/2020
- 14 minut do przeczytania
-
- j
- f
- s
obsługa błędów jest częścią życia, jeśli chodzi o pisanie kodu. Często możemy sprawdzić i zweryfikować warunki oczekiwanego zachowania. Gdy dzieje się coś nieoczekiwanego, przechodzimy do obsługi wyjątków., Możesz łatwo obsługiwać wyjątki generowane przez KOD innych osób lub możesz generować własne wyjątki dla innych do obsługi.
Uwaga
Oryginalna wersja tego artykułu pojawiła się na blogu napisanym przez @KevinMarquette. Zespół ThePowerShell dziękuje Kevinowi za udostępnienie nam tych treści. Zapraszam na jego bloga atPowerShellExplained.com.
podstawowa terminologia
zanim przejdziemy do tej terminologii, musimy omówić kilka podstawowych terminów.
wyjątek
wyjątek jest jak zdarzenie, które jest tworzone, gdy normalna obsługa błędów nie może poradzić sobie z problemem.,Próba podzielenia liczby przez zero lub brak pamięci to przykłady czegoś, co tworzy wyjątek. Czasami autor kodu, którego używasz, tworzy wyjątki dla pewnych problemów, gdy się one zdarzają.
Throw and Catch
gdy wystąpi wyjątek, mówimy, że wyjątek jest wyrzucany. Aby obsłużyć wyrzucony wyjątek, musisz go złapać. Jeśli wyjątek zostanie wyrzucony i nie zostanie złapany przez coś, skrypt przestanie działać.
stos wywołań
stos wywołań jest listą funkcji, które wywołały się nawzajem., Gdy funkcja jest wywoływana, jest dodawana do stosu lub na górze listy. Gdy funkcja kończy lub powraca, jest usuwana ze stosu.
gdy wyjątek jest wyrzucany, ten stos wywołań jest sprawdzany w celu przechwycenia funkcji obsługi wyjątków.
błędy kończące i nie kończące
wyjątkiem jest zazwyczaj błąd kończący. Rzucony wyjątek jest albo be caught, albo kończy bieżące wykonanie. Domyślnie, błąd nie kończący jest generowany przez Write-Error
I dodaje błąd do strumienia wyjściowego bez rzucania wyjątku.,
zwracam na to uwagę, ponieważWrite-Error
I inne błędy nie kończące nie powodującatch
.
Połykanie wyjątku
To jest, gdy wyłapujesz błąd tylko po to, aby go stłumić. Zrób to z ostrożnością, ponieważ może to sprawić, że problemy z roubleshooting będą bardzo trudne.
Podstawowa składnia poleceń
oto krótki przegląd podstawowej składni obsługi wyjątków używanej w PowerShell.
Throw
aby utworzyć własne Zdarzenie wyjątku, rzucamy wyjątek ze słowem kluczowym throw
.,
function Start-Something{ throw "Bad thing happened"}
spowoduje to utworzenie wyjątku uruchomieniowego, który jest błędem kończącym działanie. Jest obsługiwany przez catch
w funkcji wywołania lub kończy skrypt z takim Komunikatem.
Write-Error-ErrorAction Stop
wspomniałem, żeWrite-Error
domyślnie nie wyświetla błędu zakończenia. Jeśli podasz-ErrorAction Stop
, Write-Error
generuje błąd zakończenia, który może być obsługiwany za pomocącatch
.,
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Dziękujemy Lee Dailey za przypomnienie o użyciu-ErrorAction Stop
w ten sposób.
Cmdlet-ErrorAction Stop
Jeśli podasz -ErrorAction Stop
na dowolnej zaawansowanej funkcji lub cmdletie, zmieni to wszystkie Write-Error
na błędy kończące, które zatrzymują wykonanie lub które mogą być obsługiwane przez catch
.,
Start-Something -ErrorAction Stop
Try/Catch
sposób obsługi wyjątków działa w PowerShell (i wielu innych językach) jest to, że najpierwtry
jako sekcja kodu i jeśli rzuca błąd, możnacatch
to. Oto szybka próbka.
skryptcatch
uruchamia się tylko wtedy, gdy wystąpił błąd zakończenia. Jeśli try
wykonuje się poprawnie, to pomija catch
.,
Try/Finally
czasami nie musisz obsługiwać błędu, ale nadal potrzebujesz kodu do wykonania, jeśli exceptionhappens lub nie. Skryptfinally
robi dokładnie to.
spójrz na ten przykład:
za każdym razem, gdy otwierasz lub łączysz się z zasobem, powinieneś go zamknąć. JeśliExecuteNonQuery()
rzuca wyjątek, połączenie nie jest zamknięte. Oto ten sam kod wewnątrz bloku try/finally
.
w tym przykładzie połączenie zostanie zamknięte, jeśli wystąpi błąd. Jest również zamknięty, jeśli nie ma noerror., Skryptfinally
jest uruchamiany za każdym razem.
ponieważ nie łapiesz wyjątku, to i tak dostaje się do stosu wywołań.
spróbuj/Złap/wreszcie
doskonale jest używaćcatch
Ifinally
razem. Przez większość czasu będziesz używać jednego lub drugiego, ale możesz znaleźć scenariusze, w których używasz obu.
$PSItem
teraz, gdy mamy podstawy z drogi, możemy kopać trochę głębiej.,
wewnątrz blokucatch
znajduje się zmienna automatyczna ($PSItem
lub$_
) typuErrorRecord
, która zawiera szczegóły dotyczące wyjątku. Oto szybki przegląd niektórych klawiatur.
dla tych przykładów użyłem nieprawidłowej ścieżki wReadAllText
do wygenerowania tego wyjątku.
::ReadAllText( '\\test\no\filefound.log')
PSItem.ToString()
to daje najczystszą wiadomość do użycia w logowaniu i ogólnym wyjściu., ToString()
izautomatycznie wywołany, jeśli$PSItem
jest umieszczony wewnątrz łańcucha.
catch{ Write-Output "Ran into an issue: $($PSItem.ToString())"}catch{ Write-Output "Ran into an issue: $PSItem"}
$PSItem.InvocationInfo
Ta właściwość zawiera dodatkowe informacje zebrane przez PowerShell o funkcji lub skrypcie, gdzie został wyrzucony wyjątek. Poniżej znajduje sięInvocationInfo
z przykładowego wyjątku, który Icreated.
ważne szczegóły tutaj pokazują ScriptName
, Line
kodu i ScriptLineNumber
gdzie rozpoczęło się wywołanie.
$PSItem.,ScriptStackTrace
Ta właściwość pokazuje kolejność wywołań funkcji, które doprowadziły do kodu, w którym został wygenerowany wyjątek.
wykonuję tylko wywołania do funkcji w tym samym skrypcie, ale to śledziłoby wywołania, gdyby zaangażowane były Skrypty multipleksowe.
$PSItem.Wyjątek
jest to rzeczywisty wyjątek, który został wyrzucony.
$PSItem.Wyjątek.Message
jest to ogólna wiadomość opisująca wyjątek i stanowi dobry punkt wyjścia podczas roubleshootingu. Większość WYJĄTKÓW ma domyślną wiadomość, ale może być również ustawiona na coś niestandardowego, gdy wyjątek jest wyrzucany.,
PS> $PSItem.Exception.MessageException calling "ReadAllText" with "1" argument(s): "The network path was not found."
jest to również wiadomość zwracana podczas wywoływania $PSItem.ToString()
jeśli naErrorRecord
nie było żadnego ustawionego.
$PSItem.Wyjątek.Wyjątki InnerException
mogą zawierać wewnętrzne wyjątki. Często dzieje się tak, gdy kod, który wywołujesz, przyciąga wyjątek i rzuca inny wyjątek. Oryginalny wyjątek jest umieszczany wewnątrz nowego wyjątku.
PS> $PSItem.Exception.InnerExceptionMessageThe network path was not found.
wrócę do tego później, gdy będę mówił o ponownym wyrzucaniu WYJĄTKÓW.
$PSItem.Wyjątek.,StackTrace
To jest StackTrace
dla wyjątku. Pokazałem ScriptStackTrace
powyżej, ale ten jest dla wywołania zarządzanego kodu.
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)at CallSite.Target(Closure , CallSite , Type , String )
ten ślad stosu otrzymuje się tylko wtedy, gdy zdarzenie jest wyrzucane z kodu zarządzanego. Dzwonię do A.Funkcja NETframework działa bezpośrednio, więc to wszystko, co widzimy w tym przykładzie. Ogólnie rzecz biorąc, gdy patrzysz na ślad stosu, szukasz miejsca, w którym kończy się kod i zaczynają się połączenia systemowe.
praca z wyjątkami
wyjątki są czymś więcej niż tylko podstawową składnią i właściwościami WYJĄTKÓW.,
Przechwytywanie typowanych WYJĄTKÓW
możesz być selektywny z wyjątkami, które wyłapujesz. Wyjątki mają typ i możesz określićtyp wyjątku, który chcesz złapać.
typ wyjątku jest sprawdzany dla każdegocatch
bloku, dopóki nie zostanie znaleziony jeden, który pasuje do twojego exception.It ważne jest, aby zdać sobie sprawę, że wyjątki mogą dziedziczyć po innych wyjątkach. W powyższym przykładzieFileNotFoundException
dziedziczy z IOException
. Więc jeśli IOException
był pierwszy, to zostanie wywołany., Tylko jeden blok catch jest wywoływany, nawet jeśli istnieje wiele dopasowań.
gdybyśmy mieli System.IO.PathTooLongException
, IOException
pasowałby, ale gdybyśmy mieliInsufficientMemoryException
to nic by go nie złapało i propagowałoby się na stosie.
Przechwytywanie wielu typów jednocześnie
możliwe jest przechwytywanie wielu typów wyjątków za pomocą tej samej instrukcjicatch
.
Dziękuję za zasugerowanie tego dodatku.
rzucanie wpisanych WYJĄTKÓW
możesz rzucać wpisane wyjątki w PowerShell., Zamiast wywoływaćthrow
ciągiem znaków:
throw "Could not find: $path"
użyj akceleratora WYJĄTKÓW takiego jak:
throw "Could not find: $path"
ale musisz podać wiadomość, gdy robisz to w ten sposób.
można również utworzyć nową instancję wyjątku do wyrzucenia. Wiadomość jest opcjonalna, ponieważ system ma domyślne wiadomości dla wszystkich wbudowanych WYJĄTKÓW.
throw ::new()throw ::new("Could not find path: $path")
Jeśli nie używasz PowerShell 5.0 lub wyższej, musisz użyć starszego podejściaNew-Object
.,
używając typowanego wyjątku, możesz (lub inni) wyłapać wyjątek według typu, o którym mowa w sekcji previous.
Write-Error-Exception
możemy dodać te wpisane wyjątki doWrite-Error
I nadal możemycatch
błędów przez exceptiontype. Użyj Write-Error
jak w tych przykładach:
następnie możemy go złapać w następujący sposób:
catch { Write-Log $PSItem.ToString()}
duża lista wyjątków .NET
skompilowałem listę główną z pomocą społeczności Reddit/r/PowerShell, która zawiera.,NET exceptions to complete this post.
- duża lista wyjątków.NET
zaczynam od wyszukania na tej liście wyjątków, które wydają się być dobre dla mysituation. Powinieneś spróbować użyć WYJĄTKÓW w podstawowej przestrzeni nazw System
.
wyjątki to obiekty
Jeśli zaczniesz używać wielu wpisywanych WYJĄTKÓW, pamiętaj, że są to obiekty. Różne wyjątki mają różne konstruktory i właściwości., Jeśli spojrzymy na dokument FileNotFoundExceptiondocumentation dla System.IO.FileNotFoundException
, zobaczymy, że możemy przekazać wiadomość i ścieżkę pliku.
::new("Could not find file", $path)
i ma właściwośćFileName
, która wyświetla tę ścieżkę do pliku.
catch { Write-Output $PSItem.Exception.FileName}
należy zapoznać się z dokumentacją.NET dla innych konstruktorów i właściwości obiektu.
ponowne rzucenie wyjątku
Jeśli wszystko, co masz zamiar zrobić w swoim catch
bloku jest throw
ten sam wyjątek, to nie catch
to., Należy tylkocatch
wyjątek, który planujesz obsłużyć lub wykonać jakąś akcję, gdy się pojawi.
są momenty, w których chcesz wykonać akcję na wyjątku, ale ponowne wyrzucenie wyjątku może z tym poradzić. Możemy napisać wiadomość lub zapisać problem blisko miejsca, w którym go odkryliśmy, ale poradzimy sobie z problemem dalej na stosie.
catch{ Write-Log $PSItem.ToString() throw $PSItem}
Co ciekawe, możemy wywołaćthrow
z poziomucatch
I ponownie rzuca currentexception.,
catch{ Write-Log $PSItem.ToString() throw}
chcemy ponownie wyrzucić wyjątek, aby zachować oryginalne informacje o wykonaniu, takie jak skrypt źródłowy i numer linii. Jeśli w tym momencie rzucimy nowy wyjątek, ukryje się on w miejscu, w którym wyjątek się rozpoczął.
ponowne rzucenie nowego wyjątku
Jeśli złapiesz wyjątek, ale chcesz rzucić inny, powinieneś zagnieżdżać oryginalną wyjątek wewnątrz nowego. Pozwala to komuś w dół stosu uzyskać do niego dostęp jako$PSItem.Exception.InnerException
.
catch{ throw ::new('Could not access field',$PSItem.Exception)}
$PSCmdlet.,ThrowTerminatingError ()
jedyną rzeczą, której nie lubię w używaniu throw
dla surowych wyjątków jest to, że messagepoints błędu w throw
I wskazuje, że linia jest tam, gdzie problem.
mając komunikat o błędzie powiedz mi, że mój skrypt jest uszkodzony, ponieważ zadzwoniłemthrow
on line 31 jest komunikat abad dla użytkowników skryptu, aby zobaczyć. Nie mówi im nic użytecznego.
Dexter Dhami wskazał, że mogę użyćThrowTerminatingError()
, aby to poprawić.,
jeśli założymy, że ThrowTerminatingError()
został wywołany wewnątrz funkcji o nazwieGet-Resource
, to jest to błąd, który możemy zobaczyć.
Czy widzisz jak wskazuje na funkcjęGet-Resource
jako źródło problemu? To mówi użytkownikowi coś użytecznego.
ponieważ$PSItem
jestErrorRecord
, możemy również użyćThrowTerminatingError
w ten sposób ponownie rzucić.,
catch{ $PSCmdlet.ThrowTerminatingError($PSItem)}
powoduje to zmianę źródła błędu na Cmdlet i ukrycie wewnętrznych funkcji przed użytkownikami Twojego Cmdlet.
Try może wytworzyć błędy zakończenia
Kirk Munro zwraca uwagę, że niektóre wyjątki to tylko błędy zakończenia, gdy są wykonywane wewnątrz blokutry/catch
. Oto przykład, który dał mi, który generuje wyjątek Runtime divide by zero.
function Start-Something { 1/(1-1) }
następnie wywołaj go w ten sposób, aby zobaczyć, jak generuje błąd i nadal wysyła wiadomość.,
&{ Start-Something; Write-Output "We did it. Send Email" }
ale umieszczając ten sam kod wewnątrz try/catch
, widzimy coś innego.
try{ &{ Start-Something; Write-Output "We did it. Send Email" }}catch{ Write-Output "Notify Admin to fix error and send email"}
widzimy, że błąd staje się błędem kończącym i nie wysyła pierwszej wiadomości. Nie podoba mi się to, że możesz mieć ten kod w funkcji i działa ON inaczej, jeśli ktoś używa try/catch
.
sam nie miałem z tym problemów, ale warto o tym pamiętać.
$PSCmdlet.,ThrowTerminatingError () wewnątrz try/catch
jednym z niuansów$PSCmdlet.ThrowTerminatingError()
jest to, że tworzy błąd kończący w yourCmdlet, ale zmienia się w błąd nie kończący po opuszczeniu Twojego Cmdlet. To pozostawia burdenon rozmówcy funkcji, aby zdecydować, jak poradzić sobie z błędem. Mogą zmienić go z powrotem w błąd aterminacji, używając -ErrorAction Stop
lub wywołując go z poziomu try{...}catch{...}
.,
szablony funkcji publicznych
ostatni sposób miałem z mojej rozmowy z Kirk Munro było to, że umieszczatry{...}catch{...}
wokół każdego begin
, process
I end
Blokuj wszystkie jego zaawansowane funkcje. W tych typowych blokach catch ma pojedynczą linię używając$PSCmdlet.ThrowTerminatingError($PSItem)
, aby poradzić sobie ze wszystkimi wyjątkami opuszczającymi jego funkcje.
function Start-Something{ param() process { try { ... } catch { $PSCmdlet.ThrowTerminatingError($PSItem) } }}
ponieważ wszystko jest wtry
w jego funkcjach wszystko działa konsekwentnie., To również daje czyste błędy użytkownikowi końcowemu, który ukrywa kod wewnętrzny z wygenerowanego błędu.
Pułapka
skupiłem się natry/catch
aspekcie WYJĄTKÓW. Ale jest jedna funkcja, o której muszę wspomnieć, zanim to zakończymy.
atrap
jest umieszczany w skrypcie lub funkcji, aby wychwycić wszystkie wyjątki występujące w tym zakresie. Gdy wystąpi wyjątek, kod w trap
jest wykonywany, a następnie normalny kod jest kontynuowany. Jeśli zdarzy się wiele wyjątków, to pułapka jest wywoływana w kółko.,
ja osobiście nigdy nie zastosowałem tego podejścia, ale widzę wartość w skryptach admin lub controller, które nie zawierają żadnych WYJĄTKÓW, a następnie nadal są wykonywane.
Uwagi końcowe
dodanie odpowiedniej obsługi wyjątków do skryptów nie tylko czyni je bardziej stabilnymi, ale także ułatwia rozwiązywanie problemów z tymi wyjątkami.
spędziłem dużo czasu rozmawiającthrow
ponieważ jest to podstawowa koncepcja, gdy mówimy o exceptionhandling., PowerShell dał nam również Write-Error
, który obsługuje wszystkie sytuacje, w których można użyćthrow
. Więc nie myśl, że musisz używać throw
po przeczytaniu tego.
teraz, gdy poświęciłem czas, aby napisać o obsłudze WYJĄTKÓW w tym szczególe, zamierzam przełączyć się na użycieWrite-Error -Stop
do generowania błędów w moim kodzie. Zamierzam również skorzystać z rady i zrobić ThrowTerminatingError
mój program obsługi wyjątków goto dla każdej funkcji.