Centrum Skrypciarzy - Systemy Operacyjne

Czy można za pomocą Windows PowerShell można włączyć powiadomienia o przerwaniu działania aplikacji?

Udostępnij na: Facebook

Skrypciarze odpowiadają na Wasze pytania

Cześć Skrypciarze!

Witamy w rubryce TechNet, w której Skrypciarze z firmy Microsoft odpowiadają na częste pytania dotyczące używania skryptów w administracji systemu. Jeśli macie jakieś pytania z tej dziedziny, zachęcamy do wysłania e-maila na adres: scripter@microsoft.com. Nie możemy zagwarantować odpowiedzi na każde otrzymane pytanie, ale staramy się jak możemy.

Czy można za pomocą Windows PowerShell można włączyć powiadomienia o przerwaniu działania aplikacji?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Używamy w firmie opracowanej przez nas aplikacji, która się niestety lubi zawieszać. W takim wypadku musimy przejść długą procedurę ponownego uruchomienia. Czy jest jakiś sposób włączenia powiadomień o zawieszeniu aplikacji za pomocą Windows PowerShell?

-- OI

Cześć Skrypciarze! Odpowiedź

Cześć, OI. Wiesz co, z początku Skrypciarz piszący te słowa pomyślał sobie: „Nie ma mowy, nie odpowiadam dziś na żadne pytania”. Skrypciarz postanowił ogłosić strajk w proteście wobec ucisku ze strony Redaktorki, która upiera się, by używać poprawnych form gramatycznych i pisać z sensem. Niestety, OI, szybki cios w głowę złamał postanowienie Skrypciarza i oto gotowy skrypt. A żeby tę wiedźmę… znaczy, chciałbym tej miłej i mądrej damie życzyć wszystkiego najlepszego!

Jak się okazuje, obsługa powiadomień o zdarzeniach za pomocą Windows PowerShell wymaga nieco sprytu; dzieje się tak dlatego, że komandlet Get-WMIObject skryptów PowerShell nie obsługuje kwerend związanych z takimi powiadomieniami. (Więcej na temat kwerend powiadomień o zdarzeniu można się dowiedzieć z odpowiedniego webcastu Skrypciarzy (j.ang.).) Dlaczego tak jest? Szczerze mówiąc, nie wiadomo, a nie chce nam się sprawdzać (miejmy nadzieję, że Redaktorka tego nie zauważy, bo każe nam tego szukać!). Tymczasem jednak interesuje nas tylko to, że poczciwe WMI nam nie pomoże w ustawieniu powiadomienia o przerwaniu działania procesu. Musimy wymyślić coś innego.

Jedna z możliwości opiera się na użyciu komandletu Get-Process. Z opisu przesłanego przez OI wynika, że nie chodzi o informacje o przerwaniu dowolnego procesu. Chodzi o wybrany, ściśle określony proces (na potrzeby przykładu zakładamy, że będzie to Kalkulator). Dzięki temu możemy napisać skrypt powiadomień o zdarzeniu zajmujący zaledwie trzy wiersze kodu:

$a = get-process calc

$a.waitforexit()

"Calculator has stopped running."

Zaczynamy od zastosowania komandletu Get-Process, który połączy nas z procesem o nazwie Calc. (Jeśli nie znamy nazwy, to żaden kłopot – odpowiada ona nazwie pliku wykonywalnego, nie licząc rozszerzenia.) Zakładamy, że mamy tylko jeden proces o nazwie Calc. Co zrobić, jeśli na komputerze uruchomionych jest kilka wystąpień Kalkulatora? W takim razie zadziała pierwszy wiersz kodu – uzyskamy kolekcję obejmującą wszystkie wystąpienia Kalkulatora – ale drugi już nie. Nie panikujmy jednak – za chwilę pokażemy bardziej uniwersalny skrypt do monitorowania procesów.

Mając już łapę na procesie Calc, możemy wywołać metodę WaitForExit():

$a.waitforexit()

Co dzięki temu uzyskamy? Otóż w ten sposób „blokujemy” skrypt, co znaczy, że zawiesi on działanie, czekając aż interesujący nas proces przestanie działać. Kiedy to się stanie, uruchomiony zostanie trzeci, ostatni wiersz kodu, który wyświetli komunikat o przerwaniu działania Kalkulatora.

Skrypt działa tak, jak chcieliśmy. Nie znaczy to rzecz jasna, że jest idealny. I tak, skrypt kończy działanie wraz z Kalkulatorem, co znaczy, że trzeba uruchamiać go ponownie za każdym razem, gdy uruchamiamy Kalkulator.

Ale nie ma się co martwić, bo tutaj mamy inny skrypt, załatwiający ten problem:

$b = 1



do 

    {

        $a = get-process calc

        $a.waitforexit()

        Invoke-Item c:\windows\system32\calc.exe



    } 

while ($b -eq 1)

Jak to działa? Uruchamiamy pętlę Do, działającą dopóki zmienna $b ma wartość 1. (A zmienna ta zawsze będzie równa 1, czyli pętla nigdy nie przerwie działania.) Wewnątrz pętli ponownie się łączymy z procesem Calc i wywołujemy metodę WaitForExit(). Zauważmy jednak, co się dzieje, kiedy zakończy się działanie Kalkulatora. Nie wyświetlamy wtedy wiadomości, ale używamy komandletu Invoke-Item, który go ponownie uruchamia:

Invoke-Item c:\windows\system32\calc.exe

Po ponownym uruchomieniu Kalkulatora, skrypt znowu zaczyna go monitorować. Nie wiemy, o co chodzi z „długą procedurą”, potrzebną by uruchomić proces, o którym mówi OI, ale jeśli można zrobić to programistycznie, to czemu nie. Rzecz jasna nie musimy się ograniczać do ponownego uruchamiania procesu; możemy także zgłosić przerwanie działania, np. wyświetlając odpowiedni komunikat albo zapisując coś w pliku dziennika czy dzienniku zdarzeń – cokolwiek nam pasuje.

Uwaga. Jak mówiliśmy, powyższy skrypt będzie działał bez przerwy. Jedynym sposobem jego wyłączenia jest przerwanie działania odpowiedniego wystąpienia Windows PowerShell. Skrypt można tak zmodyfikować, aby zatrzymywał się np. po naciśnięciu klawisza Q. Niestety nie mamy miejsca, aby to opisać w dzisiejszym artykule.

Podobnie jak pierwszy opisany skrypt, tak i ten zadziała pod warunkiem, że mamy tylko jedno wystąpienie interesującego nas procesu; kilka uruchomionych Kalkulatorów równa się kłopoty. Poza tym, skrypt ten działa jedynie dla procesów – komandlet Get-Process zawiera metodę WaitForExit(). Nie musi nam to odpowiadać w wypadku innych elementów.

Pamiętając o tym, spójrzmy na bardziej uniwersalny skrypt do monitorowania zdarzeń, korzystający z platformy .NET. Teraz pokażemy kod, a za moment krótko go opiszemy:

$a = 0



$timespan = New-Object System.TimeSpan(0, 0, 1)

$scope = New-Object System.Management.ManagementScope("\\.\root\cimV2")

$query = New-Object System.Management.WQLEventQuery `

    ("__InstanceDeletionEvent",$timespan, "TargetInstance ISA 'Win32_Process'" )

$watcher = New-Object System.Management.ManagementEventWatcher($scope,$query)



do 

    {

        $b = $watcher.WaitForNextEvent()

        $b.TargetInstance.Name

    } 

while ($a -ne 1)

Skrypt ten co sekundę sprawdza, czy jakieś procesy nie zniknęły. Jak ustawić częstotliwość sprawdzania? To proste, służy do tego obiekt System.Timespan:

$timespan = New-Object System.TimeSpan(0, 0, 1)

Jak widać, tworząc go, użyliśmy trzech parametrów – 0, 0 i 1. Pierwsze 0 oznacza godziny, drugie – minuty, a jedynka – sekundy. Jak ustawić sprawdzanie co 15 minut i 30 sekund? Za pomocą takiego wiersza kodu:

$timespan = New-Object System.TimeSpan(0, 15, 30)

Skąd wiemy, że skrypt będzie szukał usuniętych procesów? Jest to podane w kwerendzie WQLEventQuery:

$query = New-Object System.Management.WQLEventQuery `

    ("__InstanceDeletionEvent",$timespan, "TargetInstance ISA 'Win32_Process'" )

Jasne? Szukamy wystąpień klasy __InstanceDeletionEvent, będących jednocześnie procesami (czyli takich, które należą do klasy Win32_Process).

Uwaga. Przyznajemy, że to bardzo skrótowe wyjaśnienie terminów __InstanceDeletionEvent i TargetInstance. Więcej informacji można znaleźć we wspomnianym webcaście (j.ang.).

Utworzywszy wystąpienie klasy System.Management.ManagementEventWatcher, uruchamiamy kolejną pętlę działającą bez końca i czekamy na zakończenie działania jakiegoś procesu. Kiedy tak się stanie, poniższy wiersz kodu spowoduje wyświetlenie właściwości Name, czyli nazwy:

$b.TargetInstance.Name

Rzecz jasna moglibyśmy zakombinować tak, aby wiadomość była wyświetlania tylko w wypadku, gdy działanie zakończy wybrany proces. Mamy nadzieję, że nasi czytelnicy sami sobie z tym poradzą.

 Do początku strony Do początku strony

Centrum Skrypciarzy - Systemy Operacyjne