Każdy chcę 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 miejsc 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 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 na Stop 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ą na SilentlyContinue
  • bez Try-Catch-Finally za to z -ErrorAction ustawionym na SilentlyContinue

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 wykorzystaniu Try-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 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

22 Najważniejsze Wskazówki Pisania Skryptów PowerShell

Mateusz Nadobnik

Zachwycony językiem skryptowym Windows PowerShell. Swoją wiedzę, doświadczenia i spostrzeżenia opisuję na blogu.

read more