
Każdy chce aby jego kod wykonywał się zawszę poprawnie, zawszę bezbłędnie. Niestety prawdopodobieństwo, że tak nie będzie rośnie wraz ze złożonością skryptu. Dlatego przewidujemy miejsca narażone na wystąpienie błędów i myślimy od razu o ich obsłudze. A jeśli obsługa błędów to najczęściej blok Try-Catch-Finally
.
Teraz pytanie do Ciebie. Zdarzyło Ci się kiedyś napisać kod, który mimo błędów nie został przechwycony przez Catch
? Jeśli tak to, postaram Ci się wyjaśnić, dlaczego tak się stało, jak to naprawić i jaka jest alternatywa dla Try-Catch-Finally
.
Zacznijmy jednak od zaszłości.
Trap
Windows PowerShell 1.0 umożliwiał obsługę błędów (i nadal umożliwia) za pomocą instrukcji trap
. Słowo kluczowe Trap
określa listę instrukcji do uruchomienia w przypadku wystąpienia błędu zakończenia. Polecenie mało znane i rzadko spotykane.
Try-Catch-Finally
Od Microsoft PowerShell 2.0 wprowadzono znaną już chociażby z języka C# konstrukcję Try-Catch-Finally
.
Blok Try
to sekcja kodu, która monitoruje błędy. To tutaj wykonywany jest kod i jeśli w tym czasie wystąpi błąd powodujący zamknięcie programu, następuję jego przechwycenie przez najbliższy blok Catch
. Te dwa bloki są ze sobą powiązane i muszą występować razem.
Ostatni blok (nieobowiązkowy) to Finally
, w którym instrukcje zostaną wykonane niezależnie od rezultatów poprzednich bloków.
W opisie konstrukcji
Try-Catch-Finally
padły kluczowe słowa, powodujące zamknięcie programu i to warto zapamiętać.
Warto też, wiedzieć, że Microsoft PowerShell rozróżnia dwa typy błędów, powodujące i niepowodujące zamknięcie programu. Z angielskiego terminating i non-terminating errors.
Terminating i non-terminating errors
Czym się różnią? Już odpowiadam.
Błędy powodujące (terminating errors) zakończenie skryptu to takie, które nie są w żaden sposób przechwycone i nie ma możliwości obsługi wyjątku np. wewnątrz funkcji. Nie ma innej możliwości, tylko całkowite zatrzymanie wykonywania skryptu i zwrócenie czerwonego błędu.

Chyba że użyjemy Try-Catch-Finally
i obsłużymy błąd.

Natomiast błędy niepowodujące (non-terminating errors) zakończenia pracy PowerShell to takie, które potrafią być obsłużone bezpośrednio przez funkcję. Zostają wyświetlone na ekran oraz przekazane do specjalnej zmiennej tablicowej $Error
.
Jednak co najważniejsze, błędy tego typu nie zostaną obsłużone przez instrukcje Try-Catch-Finally.
Blok Catch
jest po prostu pomijany.

Jak widać, polecenie Get-Item
spowodowało błąd, mimo to kontynuowało działanie, pomijając przy tym blok Catch
.
Jak obługiwać błędy niepowodujące zakończenia?
Można to robić na kilka sposobów:
-
Try-Catch-Finally
oraz$ErrorActionPreference
ustawione na Stop (dla całego skrypty)
Try-Catch-Finally
oraz-ErrorAction
ustawione naStop
na poziomie cmdletu, funkcji – rozwiązanie zalecane

W jednym i drugim przypadku program został całkowicie przerwany, mimo że ścieżka do drugiego folderu była poprawna i wykonywanie teoretycznie mogło być kontynuowane.
- bez
Try-Catch-Finally
z zmienną$ErrorActionPreference
ustawioną naSilentlyContinue
- bez
Try-Catch-Finally
za to z-ErrorAction
ustawionym naSilentlyContinue
W tych przypadkach kod kontynuuje wykonywanie, ale również zwraca w przyjazny sposób komunikat o błędzie.

Podsumowanie
Po pierwsze zrozumienie i pamiętanie o różnicach pomiędzy terminating i non-terminating errors bardzo pomaga w późniejszym radzeniu sobie z nimi.
Po drugie, podejście do obsługi błędów w PowerShell z pozoru jest łatwe, gdy pomyślimy o instrukcji Try-Catch-Finally
. Jednak ze względów na swoje niuanse nieraz dobrze rozważyć alternatywy. Dużo mniej problematyczne może okazać się wyciszenia błędów, kontynuowania i sprawdzania zmiennej $?
.
Jednak, gdy skrypt musimy przerwać przy najmniejszym błędzie, dobrą alternatywą będzie skorzystanie z konstrukcji Try-Catch-Finally
razem z zmienną globalną $ErrorActionPreference
ustawioną na Stop
.
Reasumując, nie ma jednego właściwego podejścia. Wszystko zależy potrzeby konkretnego skryptu.
Na końcu kilka ważnych zagadnień, których nie wyjaśniłem wcześniej, a są ważne w kontekście obsługi błędów.
$_
– zmienna nie do końca związana z obsługą błędów. Jednak przy wykorzystaniuTry-Catch-Finally
, błędów można szukać właśnie tutaj$?
– zawiera stan wykonania ostatniej operacji.$true
oznacza, że operacja zakończyła się pomyślnie bez żadnych błędów.$false
wskazuje całkowitą niepowodzenie lub częściowy sukces. Uwaga: dla pliku wykonywalnego systemu Windows sprawdzany jest kod wyjścia. Kod wyjścia 0 będzie interpretowany jako sukces i niezerowy jako błąd. Niektóre aplikacje konsoli Windows nie respektują tej konwencji, więc zwykle lepiej jest skontrolować$LASTEXITCODE
, aby określić wynik działania.$LASTEXITCODE
– kod wyjścia ostatniego pliku wykonywalnego systemu Windows, wywołanego w sesji.$Error
– tablica błędów, które wystąpiły w bieżącej sesji. Błędy są zawsze wstawiane na początku tablicy. W rezultacie ostatni błąd zawsze znajduje się w indeksie 0 –$Error[0]
.$MaximumErrorCount
– określa rozmiar tablicy$Error
. Domyślnie to 256, wartość maks.$ErrorActionPreference
– zmienna globalna, wpływa na zachowanie błędów niepowodujących przerwanie programu. Domyślnym ustawieniem jestContinue
, który dodaje wpis do kolekcji$Error
i wyświetla błąd na konsoli hosta.-ErrorAction
– wspólny parametr dostępny dla większości funkcji (Common Parameters) które określa jak polecenie reaguję na błąd niepowodujący zakończenie programu. Parametr ten zastępuję wartość zmiennej$ErrorActionPreference
dla bieżącego polecenia.
Tabela z wartościami jakie przyjmuję parametr -ErrorAction
oraz $ErrorActionPreference
i ich wypływ na działanie programu:
Message | Error | Stop | ErrorAction | ErrorActionPreference | ||
SilentlyContinue | No | Yes | No | * | * | |
Stop | Yes | Yes | Yes | * | * | |
Continue | Yes | Yes | No | *Default | *Default | Pyta, czy chcesz kontynuować. |
Inquire | Yes | Yes | No | * | * | |
Ignore | No | No | No | * | ||
Suspend | * | * | Używane przy Workflow |
Hej,
dzieki za skrypt ale mam jedno pytanie,
Probuje uruchomic prosty skrypt by sprawdzil czy jest dana Grupa Dystrybucyjna
try{
Get-DistributionGroup -Identity Test -ErrorAction Stop
}catch {
Write-Host Unable to find this group
}
Jakkolwiek bym nie zrobil zawsze dostaje error w postaci bledu nawet jesli nie sprecyzowalem catch:
The operation couldn’t be performed because object 'Test’ couldn’t be found on 'Local.Server’.
Jakies propozycje by to zlapac?
dodam ze probowalem zczytac blad i dodac wyjatek w [] metoda $Error[0].Exception.GetType().FullName ale rowniez nie wylapuje tego bledu
Cześć Michał,
Po ustawieniu zmiennej $ErrorActionPreference również catch jest pomijany?
$ErrorActionPreference = 'Stop'
try {
Get-DistributionGroup -Identity Test
}
catch {
Write-Output "Unable to find this group"
Write-Warning $_.Exception.Message
}
Hej dzięki!
Ten skrypt zadziałał.
Nie wiem czemu skupialem się tak bardzo na catch, myślałem że jest on w stanie wyłapać błędy. Zawiodłem się na nim już nie raz, jednak w tym momencie nie jest on mi już potrzebny.
Hej Michał. Super, że mogłem w jakiś sposób pomóc.
Pozdrawiam