Zaawansowane filtrowanie w Windows Event Log

Jest mało znanym faktem, że Event Log Viewer umożliwia filtrowanie nie tylko graficzne, ale także zaawansowane query uwzględniające dowolne dane zawarte we wpisach określonego typu.
Drugim mało znanym faktem jest, że Event Log Viewer umożliwia wyświetlenie i filtrowanie po tak zwanym „Correlation ID”. Oczywiście, aplikacja wpisująca rekordy do dziennika zdarzeń także musi wspierać Correlation ID, gdyż część aplikacji nie zapisuje tego pola.
Niezwykle przydatną sprawą jeśli chodzi o Correlation ID jest to, że możemy odfiltrować zdarzenia związane z jakąś jedną konkretną czynnością, która generuje szereg zdarzeń, zakończony, na przykład, błędem.
Oto przykładowe query:

1
2
3
4
5
6
7
<QueryList>
  <Query Id="0" Path="file://C:\Logs\Logs.evtx">
    <Select Path="file://C:\Logs\Logs.evtx">
*[System[Correlation[@ActivityID='{8A348658-802A-4ACB-A855-1CC57EA25B18}'] and (Level=1  or Level=2 or Level=3 or Level = 4) and TimeCreated[@SystemTime&gt;='2016-11-27T15:11:00.000Z' and @SystemTime&lt;='2016-11-21T15:12:00.000Z']]]
    </Select>
  </Query>
</QueryList>

Każdy wpis w logu systemu windows od 2008 w górę jest prezentowany w formacie XML. Query się odnosi do pól we wpisie. Niestety nie jest to wyrażenie XPATH, ale jest równie intuicyjne i proste do odczytania.
Przedstawiam przykładowy wpis z dziennika zdarzeń:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Microsoft-Windows-GroupPolicy" Guid="{AEA1B4FA-97D1-45F2-A64C-4D69FFFD92C9}" />
    <EventID>1500</EventID>
    <Version>0</Version>
    <Level>4</Level>
    <Task>0</Task>
    <Opcode>1</Opcode>
    <Keywords>0x8000000000000000</Keywords>
    <TimeCreated SystemTime="2016-11-27T15:11:31.651603600Z" />
    <EventRecordID>31607</EventRecordID>
    <Correlation ActivityID="{8A348658-802A-4ACB-A855-1CC57EA25B18}" />
    <Execution ProcessID="1288" ThreadID="31724" />
    <Channel>System</Channel>
    <Computer>ComputerName</Computer>
    <Security UserID="S-1-5-18" />
  </System>
  <EventData>
    <Data Name="SupportInfo1">1</Data>
    <Data Name="SupportInfo2">4202</Data>
    <Data Name="ProcessingMode">0</Data>
    <Data Name="ProcessingTimeInMilliseconds">0</Data>
    <Data Name="DCName" />
  </EventData>
</Event>

Najprostszą metodą do rozpoczęcia prac nad kwerendą jest wyjście od ustalenia elementarnych warunków Filtrowania poprzez wybranie opcji „Filter Current Log”.
Poniższy wycinek ekranu pokazuje przykładowe warunki filtru.
ScreenShot1
Następnie przechodzimy na zakładkę „XML” i zaznaczamy checkbox „Edit query manually”
ScreenShot2
Po zatwierdzeniu komunikatu o fakcie, iż nie będzie można tego query już więcej edytować graficznie, otrzymujemy edytor query.
ScreenShot3
Filozofia tworzenia query jest prosta. Każdy tag, na podstawie którego będzie wykonywane filtrowanie znajduje swoje odzwierciedlenie w postaci składni:

1
Tag[Warunek1 and/or Warunek2 (...)]

Oczywiście można zagnieżdżać w wyrażeniu kolejne tagi, jak również łączyć je operatorami. Każdy wpis w logu systemowym, który spełni wszystkie warunki zostanie pokazany w odfiltrowanym widoku.
Przedstawię jeszcze raz filtr, który pojawił się na początku. Zostanie on sformatowany w trochę inny sposób, należy jednak pamiętać, że query powinno być zapisane w jednej linii.

1
2
3
4
5
6
7
8
9
10
11
12
*[
    System[
        Correlation[
            @ActivityID='{8A348658-802A-4ACB-A855-1CC57EA25B18}'
        ]
        and (Level=1  or Level=2 or Level=3 or Level = 4)
        and TimeCreated[
            @SystemTime&gt;='2016-11-27T15:11:00.000Z'
            and @SystemTime&lt;='2016-11-21T15:12:00.000Z'
        ]
    ]
]
  1. Linia query mówi, że wyszukiwania należy wykonywać w dowolnych node’ach najwyższego rzędu. Tak naprawdę chodzi o node
  2. Linia nakazuje przeszukać Tag . W ten sam sposób można się odnieść do sekcji
  3. Linia nakazuje przeszukać Tag , który znajduje się wewnątrz tagu
  4. Linia definiuje warunek, w którym przyrównujemy wartość atrybutu o nazwie ActivityID do określonego ciągu znaków
  5. Linia zamyka listę warunków sprawdzanych w obrębie tagu
  6. Linia definiuje warunek sprawdzający zawartość tagu nie posiadającego żadnych subtagów, tylko wartość.
  7. Linia i kolejne definiują warunek w obrębie tagu i odnoszą się do dwóch atrybutów tego tagu.

Wprowadzenie do Failover Cluster Generic Script Resource

Dzisiaj chciałbym poświęcić trochę miejsca bardzo słabo udokumentowanej funkcjonalności Klastra.
Skrypty zasobów generycznych (Generic Script Resource) umożliwiają uruchomić w środowisku klastrowym nawet bardzo skomplikowane z punktu widzenia startu i potrzebnych zasobów usługi, które nie mają wbudowanych mechanizmów zapewniających wysoką dostępność.
W systemie Windows Failover Cluster umożliwia tworzenie skryptów zarządzających stanem zasobów JEDYNIE w języku Visual Basic (VBScript).
Ponieważ jest to technologia archaiczna, to powstały różne skrypty dostępne w sieci, służące do wywoływania innych narzędzi skryptowych. Ja posłużę się wersją uruchamiającą skrypty Powershell, gdyż jest to najlepszy obecnie dostępny język skryptowy dla systemu Windows.
Jednak, zanim dojdziemy do przedstawienia właściwych rozwiązań, chciałbym omówić filozofię tworzenia takich skryptów.
W środowisku klastrowym każdy zasób ma swojego właściciela (Owner Host). Jest host, na którym w danym momencie uruchamiany jest kod odpowiadający za obsługę zasobu. Celowo piszę „kod odpowiadający za obsługę”, gdyż może być to trojaki typ kodu:

  1. Manager Usług
  2. Biblioteka DLL zarządzająca zasobem (na przykład maszyny wirtualne)
  3. Skrypt zasobu generycznego

Po prawdzie, to implementacja zarówno managera usług jak i zasobu skryptu generycznego jest wewnętrznie realizowana przez odpowiednią DLL, ale rozróżnienie to jest przydatne dla administratorów i wdrożeniowców mających zdecydować, w jaki sposób należy zapewnić wysoką dostępność.

Cykl życia zasobu można opisać pewnymi etapami, które są charakterystyczne dla każdego właściciela. Należą do nich:

  1. Inicjalizacja managera zasobu (Open)
  2. Uruchomienie zasobu (Online)
  3. „Obejrzenie stanu zasobu” (LooksAlive)
  4. Sprawdzenie stanu zasobu (IsAlive)
  5. Wyłączenie zasobu (Offline)
  6. Zniszczenie zasobu (Terminate)
  7. Zamknięcie managera zasobu (Close)

Nazwy funkcji Callback implementowane w skrypcie zasobu Podane zostały w nawiasach.
Istotne jest wyjaśnienie różnicy między LooksAlive a IsAlive, gdyż na pierwszy rzut oka wydaje się to być dość nietypowe rozwiązanie. Callback LooksAlive jest wywoływany często, żeby zmniejszyć możliwie najbardziej czas reakcji na ewentualną awarię. Czas wykonania tego callback’a jest ograniczony do 300ms.
Dopiero, gdy ta funkcja zwróci błąd wywoływana jest funkcja IsAlive.
Ponadto raz na około minutę wywoływana jest funkcja IsAlive niezależnie od opisanego powyżej mechanizmu. Wynika to z pryncypium szybkiego wykonania LooksAlive, a w konsekwencji pobieżnego sprawdzenia, które może coś (celowo, lub nie) przeoczyć.
Dlatego manager klastra i tak sprawdza pełną diagnostykę zasobu raz na minutę. Jest to na tyle nieduży czas, że jeszcze akceptowalny dla większości usług, a z drugiej strony jest on na tyle duży, że nie wprowadza znacznego obciążenia do systemu „władającego” usługą.
Pozostałe funkcje callback ze skryptu nie wymagają dłuższego omówienia.

Bardzo ważną sprawą do omówienia, gdyż z tym się mogą wiązać błędu i w konsekwencji problemy z uruchomieniem, jest kwestia zmiennych zasobu. Każdy zasób może (i powinien) przechowywać swój stan konfiguracji w przestrzeni klastra – dzięki temu unikniemy sytuacji, w której usługa będzie zachowywać się niespójnie na różnych hostach-właścicielach zasobu.
Istotnym jest, aby definiować zmienne zasobu na samym zasobie skryptu, a nie na grupie zasobów, czy też na zasobie adresu ip albo jakimkolwiek innym zasobie w grupie. Tylko zmienne zasobu skryptu (czyli zdefiniowane dla instancji typu Generic Script Resource) będą dostępne przez Api Klastra.

API klastra reprezentowane jest przez obiekt VBScript o nazwie

1
Resource

Przykład użycia tego obiektu prezentuje poniższa funkcja:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function CheckProperties(ActionName, ErrorBaseCode)
    If Resource.PropertyExists("DebugVBA") = False Or Resource.PropertyExists("DebugPS") = False Then
        WshShell.LogEvent 1, "No Debugging Settings! Exiting."
        CheckProperties = ErrorBaseCode+1
        Exit Function
    End If
    LogEvent 4, "Enter " & ActionName & "()"
    If Resource.PropertyExists("PSScriptsPath") = False Then
        LogEvent 1, "Error " & ActionName & "(). No PSScriptsPath private property. Exiting."
        CheckProperties = ErrorBaseCode+2
        Exit Function
    End If
    If Resource.PropertyExists("QueueIndex") = False Then
        WshShell.LogEvent 1, ActionName & "(): No Cluster Queue Definition! Define QueueIndex private property. Exiting."
        CheckProperties = ErrorBaseCode+3
        Exit Function
    End If
    CheckProperties = 0
End Function