Centrum skryptów - Systemy operacyjne

Jak zrobić zapasową kopię dziennika zdarzeń w pliku tekstowym?

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.

Jak zrobić zapasową kopię dziennika zdarzeń w pliku tekstowym?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Jak można zrobić kopię zapasową dziennika zdarzeń w pliku tekstowym?

-- IMBDS

Cześć Skrypciarze! Odpowiedź

Cześć, IMBDS. Jak wiesz, poświęciliśmy całe nasze życie, by pomagać innym. (W sumie to naszym wstępnym założeniem było poświęcenie swojego życia po to, aby to inni mieli szansę pomóc nam, ale w sumie był to chyba za szczytny cel i koniec końców się po prostu rozmyśliliśmy). W naszym drugim założeniu, którego staramy się trzymać, chodzi o to, by za każdym razem, kiedy dowiemy się czegoś nowego, podzielić się tą wiedzą z resztą świata. (Wiem, wiem, jesteśmy wspaniali…)

Wczoraj na przykład dowiedzieliśmy się, że wykaz telefonów zastrzeżonych dla akwizytorów i badań marketingowych ma swoją datę przydatności do spożycia (zupełnie jak jogurt, o którym wspominałem dwa skrypty temu) i można go zastrzec tylko na 5 lat. Chociaż dałbym wiele za to, by móc zastrzec swój numer przed Skrypciarskim Synem, który to ma w zwyczaju dzwonić do mnie z ankietą „Czy nie uważasz, że … jest mi bardzo potrzebny?” lub „Czy są w domu pieniądze?” i sprytnie korzystając z techniki sprzedaży „trzy razy TAK” przystępuje do ofensywy, którą w skrócie nazwę tu wymownie „DAJ”. Pytanie zatem do Ciebie, IMBDS – czy znasz może jakiś sposób na zastrzeżenie swojego numeru chociażby na te 5 lat przed Skrypciarskim Synem?

Ufam, że tak i w podzięce dedykuję Ci ten oto skrypt. Musisz pamiętać jednak, że życie to nie rurki z kremem (szczególnie jeżeli posiadasz tak chętnie ankietującego syna jak Skrypciarski Syn) i tak jak nie da się zastrzec swojego telefonu na zawsze, tak samo nie istnieje żadna prosta metoda na to, aby zrobić kopię zapasową dziennika zdarzeń do pliku tekstowego. Metoda WMI BackupEventLog może zapisywać dane używając tylko i wyłącznie binarnego formatu dziennika zdarzeń. Oczywiście nie oznacza to, że nie można zapisać danych z dziennika zdarzeń do pliku tekstowego, oznacza to jedynie, że musisz, IMBDS, posłużyć się tym skryptem:

strComputer = "."



Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")



Set colEvents = objWMIService.ExecQuery _

    ("Select * from Win32_NTLogEvent Where LogFile='Application'")



Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objFile = objFSO.CreateTextFile("C:\Scripts\Events.txt")



For Each objEvent in colEvents

    strTimeWritten = objEvent.TimeWritten



    dtmTimeWritten = CDate(Mid(strTimeWritten, 5, 2) & "/" & _

        Mid(strTimeWritten, 7, 2) & "/" & Left(strTimeWritten, 4) _

            & " " & Mid (strTimeWritten, 9, 2) & ":" & _

                Mid(strTimeWritten, 11, 2) & ":" & Mid(strTimeWritten, 13, 2))



    dtmDate = FormatDateTime(dtmTimeWritten, vbShortDate)

    dtmTime = FormatDateTime(dtmTimeWritten, vbLongTime)



    strEvent = dtmDate & vbTab

    strEvent = strEvent & dtmTime & vbTab

    strEvent = strEvent & objEvent.SourceName & vbTab

    strEvent = strEvent & objEvent.Type & vbTab

    strEvent = strEvent & objEvent.Category & vbTab

    strEvent = strEvent & objEvent.EventCode & vbTab

    strEvent = strEvent & objEvent.User & vbTab

    strEvent = strEvent & objEvent.ComputerName & vbTab



    strDescription = objEvent.Message

    If IsNull(strDescription) Then

        strDescription = "The event description cannot be found."

    End If

    strDescription = Replace(strDescription, vbCrLf, " ")

    strEvent = strEvent & strDescription



    objFile.WriteLine strEvent

Next



objFile.Close

Przyznaję, skrypt wygląda na pierwszy rzut oka dość przerażająco i skomplikowanie, a jak wiemy, nic nie dzieje się bez przyczyny. Tu przyczyna jest dość jasna – niektóre rzeczy, o których będziemy tu wspominać po prostu są trochę skomplikowane…Bez obaw, damy sobie z nimi radę!

Skrypt zaczyna się bez większych sensacji – od połączenia się z usługą WMI na komputerze lokalnym. Tak, już uprzedzamy Wasze pytanie – TAK, skrypt będzie również działał na komputerze zdalnym, należy tylko przypisać nazwę żądanego komputera zdalnego zmiennej strComputer w następujący sposób:

strComputer = "atl-dc-01"

Nie ma za co dziękować! W końcu jak się jest Skrypciarzem, wie się takie rzeczy, ot co…

Po połączeniu się z usługą WMI uruchamiamy następujący wiersz kodu, który zwraca nam kolekcję wszystkich zdarzeń w dzienniku Application:

Set colEvents = objWMIService.ExecQuery _

    ("Select * from Win32_NTLogEvent Where LogFile='Application'")
Uwaga: Nie muszę chyba wspominać, że robienie zapasowej kopii nie dotyczy tylko dziennika zdarzeń Application, możemy to z robić z każdym (a nawet wszystkimi) dziennikiem zdarzeń. Jeżeli potrzebujecie trochę więcej informacji na ten temat (szczególnie na temat dziennika zdarzeń Security), zajrzyjcie na Microsoft Windows 2000 Scripting Guide (j.ang.).

To wszystko sprowadza nas do następujących wierszy kodu:

Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objFile = objFSO.CreateTextFile("C:\Scripts\Events.txt")

Jak już wspomnieliśmy, nie istnieje żadna wbudowana metoda na tworzenie kopii zapasowej dziennika zdarzeń w pliku tekstowym, to znaczy: nie ma takiej metody WMI, jak, na przykład, BackupAsTextFile. To oznacza, że tylko w jeden sposób jesteśmy w stanie programistycznie zachować zawartość dziennika zdarzeń w pliku tekstowym: musimy sami wpisać tę informację do pliku tekstowego. Jak się do tego zabrać? Dla początkujących: powyższych dwu wierszy kodu używamy, by utworzyć wystąpienie obiektu Scripting.FileSystemObject, a następnie do utworzenia nowego pliku tekstowego o nazwie C:\Scripts\Events.txt.

No i w tym momencie musimy zabrać się do prawdziwej roboty. Oczywiście powinniśmy byli napisać, że tak właściwie jest możliwe zapisanie dziennika zdarzeń w pliku tekstowym, ale … jeżeli oczywiście otworzy się narzędzie Event Viewer i samemu wykona się całą mozolną pracę. Jeżeli tak zrobimy, konsola Event Viewer zachowa dziennik zdarzeń jako plik rozdzielony znakami tabulacji. Zadecydowaliśmy, że będziemy używać tego samego formatu: wszystkie zdarzenia zachowamy w dzienniku zdarzeń Application w formacie rozdzielonym tabulatorami. (To znaczy, że dla każdego zdarzenia będziemy rozdzielać pola – takie elementy jak kod zdarzenia lub opis zdarzenia – za pomocą tabulatora.)

Teraz tworzymy pętlę For Each, która przechodzi przez wszystkie zdarzenia w naszej kolekcji. Wewnątrz pętli pobieramy wartość właściwości TimeWritten i przechowujemy ją w zmiennej strTimeWritten:

strTimeWritten = objEvent.TimeWritten

Tutaj sprawy nam się trochę komplikują. Jak zapewne wiecie, WMI przechowuje dane używając formatu UTC (Universal Time Coordinate), co oznacza, że wartość właściwości TimeWritten będzie wyglądała w następujący sposób:

20070905121045.578000-420

Urocze, nieprawdaż? Nie będziemy się tu teraz zajmować tym, jak to odszyfrować, dość dobre wyjaśnienie znajdziecie w przewodniku Scripting Guide (j.ang.). Wiadomo, że wartość 20070905121045.578000-420 nie wygląda tak przejrzyście na pierwszy rzut oka, jak:

9/5/2007 12:10 PM

Ale jak, na Boga, przekonwertować taką wartość UTC na coś, co mózg ludzki jest w stanie pojąć? Oczywiście w następujący sposób:

dtmTimeWritten = CDate(Mid(strTimeWritten, 5, 2) & "/" & _

    Mid(strTimeWritten, 7, 2) & "/" & Left(strTimeWritten, 4) _

        & " " & Mid (strTimeWritten, 9, 2) & ":" & _

            Mid(strTimeWritten, 11, 2) & ":" & Mid(strTimeWritten, 13, 2))

Cały opis naszego odszyfrowywania znajdziecie we wspomnianym przewodniku. Teraz zrobimy to dość szybko i pobieżnie. Nie jest to aż tak skomplikowane, pobieramy bowiem fragmenty wartości UTC i tworzymy z nich naszą własną wartość data-czas. Na przykład, pierwsze cztery cyfry wartości UTC odnoszą się do roku, zatem 20070905121045.578000-420 wskazuje na to, że mamy tu do czynienie z rokiem 2007. W naszym kodzie zatem również mamy do czynienie z rokiem 2007:

Left(strTimeWritten, 4)

Jeżeli pobawimy się jeszcze przez chwilę w Enigmę, odkryjemy, że zmienna dtmTimeWritten ma przypisaną następującą wartość:

9/5/2007 12:10:45 PM

Z czymś takim już można pracować, no nie?

Jak się okazuje, format pliku rozdzielony znakami tabulacji używany przez program Event Viewer oddziela datę (9/5/2007) od czasu (12:10:45 PM). Bardzo dobrze, ponieważ naszym następnym krokiem będzie użycie funkcji FormatDateTimei i stałych języka VBScript, takich jak vbShortDate i vbLongTime w celu przechowania daty w zmiennej dtmDate oraz czasu w zmiennej dtmTime:

dtmDate = FormatDateTime(dtmTimeWritten, vbShortDate)

dtmTime = FormatDateTime(dtmTimeWritten, vbLongTime)

Po zrobieniu powyższego, zaczynamy tworzyć pierwszy wiersz pliku tekstowego (który będzie odpowiadał informacji zawartej w pierwszym zdarzeniu w naszym dzienniku zdarzeń). Do tego właśnie służy poniższy fragment kodu:

strEvent = dtmDate & vbTab

strEvent = strEvent & dtmTime & vbTab

strEvent = strEvent & objEvent.SourceName & vbTab

strEvent = strEvent & objEvent.Type & vbTab

strEvent = strEvent & objEvent.Category & vbTab

strEvent = strEvent & objEvent.EventCode & vbTab

strEvent = strEvent & objEvent.User & vbTab

strEvent = strEvent & objEvent.ComputerName & vbTab

Dość jasne, nie? W pierwszym wierszu przypisujemy datę (reprezentowaną przez dtmDate) oraz znak tabulacji (stałą VBScript vbTab) do zmiennej strEvent. (Dlaczego akurat znak tabulacji? Ano dlatego, że chcemy, aby każde pole było rozdzielone znakiem tabulacji.) W drugim wierszu przypisujemy zmiennej strEvent aktualną wartość strEvent oraz wartość zmiennej dtmTime oraz kolejny znak tabulacji. Następnie powtarzamy kilkakrotnie cały ten proces przypisując wartości właściwości WMI takie jak SourceName, Type do zmiennej strEvent.

To prowadzi nas do następującego fragmentu kodu:

strDescription = objEvent.Message

If IsNull(strDescription) Then

    strDescription = "The event description cannot be found."

End If

strDescription = Replace(strDescription, vbCrLf, " ")

strEvent = strEvent & strDescription

Zapewne najważniejszą właściwością dziennika zdarzeń jest właściwość Message; właściwość ta daje nam (mniej lub bardziej szczegółowy) opis zdarzenia. W naszym skrypcie niestety może ona spowodować niezłe zamieszanie. Co z tym zrobić? Najlepszym sposobem na radzenie sobie z zamieszaniem jest oczywiście uniknięcie zamieszania.

O jakich problemach tu mowa? Z jednej strony właściwość Message często zawiera jeden lub kilka znaków powrotu karetki i nowego wiersza; innymi słowy wartość właściwości Message może wyglądać w sposób następujący:

Security policy in the Group policy objects has been applied successfully.



For more information, see Help and Support Center at https://go.microsoft.com/fwlink/events.asp.

Co tutaj nie gra? W plikach rozdzielonych znakami tabulacji, każda informacja dla pojedynczego zdarzenia musi mieścić się w jednym wierszu, tak się natomiast nie stanie jeżeli właściwość Message zawiera znak powrotu karetki i nowego wiersza. W rezultacie, aby zastąpić wszystkie znaki powrotu karetki i nowego wiersza (vbCrLf) pustymi znakami, musimy użyć funkcji Replace:

strDescription = Replace(strDescription, vbCrLf, " ")

Wszystko pięknie, ale sprowadza to na nas kolejną problematyczną kwestię – dla niektórych zdarzeń właściwość Message tak na dobrą sprawę nie ma wartości. Na nasze szczęście to żaden problem w konsoli Event Viewer. W takim przypadku Event Viewer wyświetli nam następujący komunikat:

The description for Event ID ( 0 ) in Source ( iPod Service ) cannot be found. The local computer 

may not have the necessary registry information or message DLL files to display messages from a 

remote computer. You may be able to use the /AUXSOURCE= flag to retrieve this description; see Help 

and Support for details. The following information is part of the event: Service started.

To jednak działa tylko i wyłącznie w konsoli Event Viewer; nasza informacja nie przechodzi do właściwości Message, Message zostaje tutaj zwrócona jako właściwość Null.

Czy to rzeczywiście taki problem? W sumie tak, ponieważ nie na się uruchomić funkcji Replace dla wartości Null. Dlatego też, zanim jeszcze wywołamy funkcję Replace, warto uruchomić poniższy fragment kodu:

If IsNull(strDescription) Then

    strDescription = "The event description cannot be found."

End If

Używamy tutaj funkcji IsNull aby określić, czy właściwość Message (przechowywana w zmiennej strDescription) nie ma aby wartości Null. Jeżeli nie ma, to bardzo dobrze, jeżeli jednak zdarzy się, że ma, to po prostu ustawiamy wartość strDescription na komunikat „The event description cannot be found”.

Po uporaniu się ze wszystkimi wartościami Null wywołujemy funkcję Replace i wpisujemy wartość strDescription na koniec naszego ciągu zdarzeń:

strEvent = strEvent & strDescription

Teraz już tylko przywołujemy metodę WriteLine aby przypisać to zdarzenie do pliku tekstowego:

objFile.WriteLine strEvent

Teraz wracamy do samego początku naszej pętli For Each i powtarzamy cały proces dla kolejnego zdarzenia w kolekcji. Po dodaniu wszystkich zdarzeń d pliku tekstowego uruchamiamy metodę Close, zamykamy plik i na tym kończymy

Mam nadzieję, że odpowiada to na Twoje pytanie, IMBDS. W podzięce za tak wyrafinowany skrypt proszę o Twój numer telefonu, na który to będę z westchnieniem ulgi przekierowywał wszystkie rozmowy przychodzące z telefonu Skrypciarskiego Syna… W sumie to czekam na kolejne pytanie od Ciebie, to może w zamian będę mógł przekierowywać wszystkie rozmowy od Skrypciarskiej Matki…

 Do początku strony Do początku strony

Centrum skryptów - Systemy operacyjne