PowerShell

Obsługa błędów w PowerShell – Try-Catch-Finally i jego niuanse

Poniżej przedstawię wam kilka faktów związanych z obsługą błędów w Windows PowerShell.

Zacznę trochę od zaszłości. W Windows PowerShell 1.0 błędy można było obsługiwać (i nadal można) za pomocą instrukcji trap. Polecenie mało znane i rzadko spotykane.

Dokumentacja: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_trap
W Microsoft PowerShell 2.0 wprowadzono znaną już chociażby z języka C# konstrukcję Try-Catch-Finally. Blok try to sekcja kodu, która obsługuję błędy. To tutaj wykonywany jest nasz kod i jeśli w tym czasie wystąpi błąd powodujący zamknięcie programu, następuję przechwycenie tego błędu przez najbliższy blok Catch. Te dwa bloki są ze sobą powiązane i zawszę muszą występować razem. Ostatni blok to Finally, w którym instrukcje zostaną wykonane niezależnie od rezultatów poprzednich bloków.

Jak pewnie zauważyłeś 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 nie powodujące zamknięcie programu. Z angielskiego terminating i non-terminating errors.

Czym one się różnią?  Błędy powodujące 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 jak zwrócenie czerwonego komunikatu na ekran chyba, że użyjemy Try-Catch-Finally.

Natomiast błędy niepowodujące zakończenia pracy PowerShell to takie które potrafią być obsłużone bezpośrednio przez funkcję. Zostają co prawda wyświetlone  na ekran oraz przekazane do specjalnej zmiennej tablicowej $Error.  Co ciekawe i ważne błędy tego typu nie są domyślnie obsługiwane przez instrukcje Try-Catch-Finally, blok Catch jest pomijany.

Zrozumienie różnic między tymi rodzajami błędów bardzo pomaga w późniejszym radzeniu sobie z czerwonymi alertami.

Jak widać polecenie Get-Item spowodowało błąd, kontynuowało działanie,  pomijając przy tym blok Catch.

Więc nasuwa się pytanie, jak takie błędy obsługiwać, przechwytywać i zwracać w elegancki sposób komunikaty o błędach?

Można to robić na kilka sposobów, np wymuszając zatrzymanie programu:

-Try-Catch-Finally oraz -ErrorAction ustawione na Stop (na poziomie funkcji)

-Try-Catch-Finally oraz $ErrorActionPreference ustawione na  Stop (dla całego skrypty)

W jednym i drugim przypadku program został przerwany mimo, że ścieżka do drugiego folderu była poprawna i wykonywanie mogło być kontynuowane.

Inny sposób, akurat w tym przypadku lepszy.

-bez Try-Catch-Finally oraz -ErrorAction ustawiony na SilentlyContinue

-bez Try-Catch-Finally oraz $ErrorActionPreference na SilentlyContinue


Tak napisany skrypt kontynuuje wykonywane ale również zwraca w przyjazny sposób komunikat o błędzie.

Podejście do obsługi błędów w PowerShell z pozoru jest łatwe gdy pomyślimy o blokach Try, Catch i Finally.  Jednak ta instrukcja ma swoje niuanse i nieraz lepiej z niej zrezygnować. Dużo mniej problematyczne może okazać się wyciszenia błędów, kontynuowania i sprawdzania zmiennej $?.

Choć warto nieraz rozważyć skorzystanie z konstrukcji Try-Catch-Finally przy ustawieniu $ErrorActionPreference na Stop aby przerywać skrypt przy najmniejszym błędzie.

Reasumując nie ma jednego właściwego podejścia, wszystko zależy od logiki samego skryptu.


Na końcu zebrane 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 wykorzystaniu Try-Catch-Finally, błędów można szukać właśnie tutaj
  • $? – zawiera stan wykonania ostatniej operacji. Prawda oznacza, że ​​operacja zakończyła się pomyślnie bez żadnych błędów. Fałsz 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 można było 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.
  • $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 jest „Continue”, 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