Get-Tail
Zwar gibt es schon sehr viele Programme, die das Ende einer Datei anzeigen (Siehe auch Tail) und selbst die Powershell hat mit dem Programm "Get-Content" und der Option "-WAIT" ein Hilfsmittel, um Dateien zeilenweise einzulesen und auf neu hinzugefügte Daten zu reagieren. Aber diese Tools sind doch entweder für den Admin oder interaktiven Gebrauch und weniger für eine automatische Verarbeitung neuer Daten. "GET-CONTENT -WAIT" z.B. liest trotzdem immer erst einmal die ganze Datei, ehe es auf mehr Input wartet.
Anforderungen
Daher überlege ich schon lange, ob ich mit eine eigene TAIL-Klasse bzw. TAIL-Objekt erstelle, welche für mich essentielle Funktionen mitbringt. Es reicht mir nicht aus, einfach nur ein Programm mit einem Dateinamen aufzurufen, welches dann die neu angefügten Daten ausgibt.
- Mehrere Dateien
Das Überwachen einer Datei ist trivial. Zumindest die Angabe von Wildcards solle möglich sein, womit dann natürlich auch verbunden ist, dass die Dateien parallel überwacht werden. Schließlich können sich alle Dateien ändern. - Wiederaufsetzen
Skripte werden auch mal beendet oder Server durchgestartet. Ein TAIL, der Änderungen in der Pausenzeit nicht erkennt verliert Daten und wenn ein Tail alles wieder einliest, gibt es doppelte Einträge. Daher sollte das Modul die letzte Position der Dateien sich merken (Registry, Datei o.ä.) und beim Abbruch oder Neustart dort wieder aufsetzen. - Dynamische Dateien addieren
Wenn während des Laufs neue Dateien angelegt werden (z.B. man überwacht das Exchange Messagetracking und sogar neu angelegte Dateien des Musters dynamisch mit überwacht werden. Wird eine Datei gelöscht und dann wieder angelegt, muss Sie natürlich auch wieder "von vorne" gelesen werden. - CSV-Format
Get-Content liest einfach nur "Text" während Import-CSV sogar Tabellen formatiert einliest und als Objekte in die Pipeline schiebt. Eine Kombination hiervon wäre wünschenswert, d.h. dass die Aufgaben eines GET-TAIL auf eine CSV-Datei auch eine entsprechende Ausgabe liefert. Genial wäre natürlich, denn das Modul optional am Anfang versuchen würde das Format der Datei (Headerzeile) aufgreifen würde. - Pipeline-Output
Als Powershell-Modul sollte per Default natürlich einfach die Zeile ausgegeben werden. Werden aber mehrere Dateien überwacht und vielleicht sogar CSV-Dateien analysiert, dann wäre ein Objekt besser. Selbst wenn "nur" Zeilen übergeben werden müssten, könnten diese ja aus unterschiedlichen Quellen sein. Insofern könnte pro Zeile durchaus ein Datensatz bestehend aus einem Zeitstempel, dem Dateinamen, Pfad und dann der Zeile entstehen. - Zeilenfilter
Wenn es die Zeit erlaubt, wäre ein Filterausdruck zumindest für die Zeilen wünschenswert. Über einen regulären Ausdruck (RegEx) könnte GET-TAIL schon filtern, welche Zeilen überhaupt zur Weiterverarbeitung gesendet werden müssen. - Timeout
Moderne Hochsprachen kennen auch "Callback"-Funktionen, d.h. das aufrufende Programm kann weiter arbeiten und wird von dem Untermodul wieder angesprungen. Powershell und viele anderen Skriptsprachen sind aber sequentiell und warten daher, bis das aufgerufene Modul zurück kommt. Insofern wäre es wichtig, dass nach einem einstellbaren Timeout das Modul wieder die Kontrolle an das Hauptprogramm mit einer leeren Zeile übergibt, so dass dieses auf andere Vorgänge reagieren kann. Auch hier wird klar, warum sich das Modul die letzten Positionen merken sollte.
Ich erwarte mir deutlich mehr als eine einfach "tail.exe". Ideal fände ich eine .NET DLL, welche als PSSnapin einfach nachgeladen werden kann. Vielleicht kann Sie auch noch per COM erreicht werden, damit die VB-Skript-Jünger auch noch etwas davon haben.
Einsatzbereiche
Nun werden Sie sich fragen, warum so was "einfaches" überhaupt entwickelt werden soll. Dazu möchte ich ein paar Beispiele aufführen. Weitere Einsatzbereiche finden Sie auch auf Tail.
- IIS-Überwachung
Es ist gar nicht mal unüblich, dass man z.B.: die Logdateien eines IIS (Webservers) in nahezu Echtzeit überwachen will. Ein Öffnen mit Notepad ist bei großen Dateien nicht mehr sinnvoll, Get-Content liest dann auch erst mal lange alte Daten und Windows-Versionen eines TAIL zeigen zwar schön an, aber man kann nicht effektiv filtern.
Powershell mit einem GET-TAIL würde hier sehr einfach eine Lösung erlauben, besonders wenn die Ausgabe per Pipeline noch gefiltert werden könnte. So könnte ich gezielt eine ClientIP, einen UserAgent, einen Benutzernamen oder spezielle URLs überwachen. - IIS-Auswertung
In Verbindung mit der Funktion die letzte Position zu speichern würde es erlauben, kleinere Auswertungen selbst durchzuführen, z.B. alle 10 Min ein Skript zu starten, um die Anzahl der OWA-Anmeldungen, ActiveSync Einträge zu ermitteln und in eine Datenbank zu importieren. - Exchange Messagetracking
Eine gefilterte Anzeige, z.B. auf "FAIL"-Meldungen würde schon helfen. - Einbindung fremder Systeme
Sehr viele Programme können keine Eventlog o.ä. schreiben aber Protokolldateien. So gibt es verschiedene SYSLOG-Daemons, die Meldungen von Routern in Dateien schreiben können. So könnten diese Dateien einfach weiter verarbeitet werden. Auch ich habe das ein oder anderen Skripte, welches Ergebnisse in Dateien protokolliert (z.B. TrackLoginEvents). Auch hier wäre eine Weiterverarbeitung möglich. - Analyse von Router Logs
Router oder auch Unified Messaging Gateways können per SYSLOG eine Meldung an einen SyslogD schreiben, der die Daten natürlich in eine Text-Datei schreiben kann. So wäre eine Weiteverarbeitung und Analyse einfach möglich.
Dateien sind eine einfache stabile und flexible Schnittstelle zur Übergabe von Informationen von einem Prozess zu einem anderen Dienst und funktionieren auch Betriebssystemübergreifend und über die meisten (Datei)-Netzwerke.
Wie verfolge ich Änderungen ?
Ich habe mir natürlich schon meine Gedanken gemacht. Es gibt gar mehrere Optionen, möglichst schnell zu erfahren, ob eine Datei sich geändert hat. Vier Verfahren konnte ich ausfindig machen:
- DIR mit "Last Modified-Timestamp
Jede Datei auf einem Dateisystem hat ein "Änderungsdatum". Ein Skript könnte also immer wie ein "DIR" periodisch das Verzeichnis pollen und so Änderungen erkennen. Ohne besondere Rechte kann das lokal gut funktionieren, da die meisten Dateisystem auch einen Cache haben. Über LAN erzeugt die aber schon eine Last, die mit dem Bedarf an Aktualität steigt. Am kritischsten ist aber zu sehen, dass nicht alle Programme eine Datei nach dem Schreiben schließen und daher das Datum nicht immer zeitnah auch aktualisiert wird. - Immer am Ende lesen
Ein zweiter Weg besteht darin, die Dateien einfach bis ans Ende zu lesen bzw. ans Ende zu springen (Wenn die Wunschposition bekannt ist) und von dort über die EOF-Marke immer mal wieder weiter zu lesen. Solange keine Dateien angehängt wurden liefert der Lesebefehl ein EOF (End of File) zurück und man wartet weiter. Es werden keine besonderen Rechte benötigt, bei lokalen Zugriffen spart der Cache entsprechende IOs. Über LAN hingegen ist diese Version zu prüfen. - NTFS Change Notification
Das NTFS-Dateisystem erlaubt die Aktivierung von Änderungsmeldungen, d.h. Windows informiert ein Programm., wenn sich in einem Verzeichnis oder einer Datei etwas tut. Dieser Ansatz wäre nahezu Echtzeit, würde auch über LAN (SMB) funktionieren und benötigt keine höheren Privilegien. In Powershell könnte so etwas wie folgt beginnen:
$watcher = [system.io.filesystemwatcher] $watcher.path = "" watcher.Filter = "*.txt" $watcher.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
- Filter Treiber / Debug-Schnittstellen
Ähnlich einen Virenscanner oder einer Archiv/HSM-Lösung könnte natürlich auch ein NTFS-Filter alle (Schreib)-Zugriffe auf Dateien überwachen (Analog zu Sysinternals Filemon) und daraus Rückschlüsse ziehen, welche Dateien wohl geändert und nachgelesen werden müssen. Diese Weg wäre "Echtzeit" aber eher überdimensioniert und nicht sehr einfach umzusetzen. Man müsste zwingend lokal und höher privilegiert sein.
Letztlich mache ich keine Vorgaben, wie die Lösung umzusetzen ist, Aus Kompatibilitätsgründen und der Frage der erforderlichen Berechtigungen könnte ich mir aber vorstellen, dass das fortgesetzte Lesen am Ende der Dateien und die Überwachung nach neuen bzw. gelöschten Dateien mit einem DIR eine sinnvolle Kombination sein könnte.
Umsetzung
Ich will hier nicht allzu viel Vorgreifen. Es kann ja sein dass es viel bessere Algorithmen zur Umsetzung gibt. Eine einfache Umsetzung in Form einer VBScript-Klasse zum fortgesetzten Lesen am Ende einer Datei habe ich auf VBSToolbox unter der URL class.tail.1.0.vbs veröffentlicht, Aber das kommt natürlich überhaupt nicht an das eigentliche Ziel heran. Hier ein paar Pseudo-Codes, die ich immer mal wieder skizziert habe.
Funktionsweise (Option1)
Überwachte Verzeichnis mit FileSystemWatcher (?)
Warte auf Modifikation
IF Dateinamenfiltermatch Dann
Hole LastPos(Datei) auf Speicher
IF LastPos = null THEN
LastPos= 0' New File
ENDIF
IF Filesize<LastPost THEN
LastPost=0 'Rollover Achtung eventuell
Doppelverarbeitung
ENDIF
Öffne Datei
Seek "lastpost"
WHILE not eof
ReadLine
WEND
End
Knifflig sind hier ein bisschen die Problematik, dass Textdateien sehr einfach mit einem Textreader gelesen werden könnten, aber dieser keine absolute Positionierung versteht. Also muss die Datei mit einem Binaryreader gelesen werden, aus dem man dann die Zeilen heraus holt aber damit umgehen muss, das die letzte Zeile vielleicht noch nicht komplett geschrieben wurde.
Natürlich könnte man auf eine aktive Reaktion auch verzichten und einfach "pollen". das ist meist sogar einfacher, weil dann das Skript einmaliug gestartet sit, die Änderungen an die Pipeline zur Verarbeitung meldet und sich dann wieder beendet.
Dazu könnte das Skript die selektierten Dateien anhand des Namens und "CreationDate" eindeutig bestimmen und die letzte Position speichern. Bei jedem Aufruf müsste nur geprüft werden, ob die Dateien nun schon länger sind und die Daten liefern. Zudem müssen neue Dateien mit der Länge "0" aufgenommen werden und nicht mehr vorhandene Dateien bereinigt werden.
Schnittstelle: Powershell Commandlet oder allgemeine COM und .NET-Klasse
Wer sich hier heranwagen will, sollte sich etwas mit C# auskennen und überlegen, welche Parameter an die Powershell übergeben werden müssen. Es muss auch nicht unbedingt ein Powershell-Commandlet sein. Es kann auch durchaus eine generische .NET Klasse sein, die man ebenso einfach per Powershell instanzieren kann, z.B.:
$tail = New-Object msxfaq.tail
$tail.filefilter = c:\test\*.log ' definiere überwachtes
Verzeichnis\Datei(en)
$tail.statusstore = filename ' Datei, zur Speicherung des Status
$tail.setfilter ( array of regex) ' Filter für relevante Zeilen
hinterlegen
$tail.savestatus ' Aktuellen Status sichern, d.h. nach erfolgter
Verarbeitung
line = $tail.getline ' Hole nächste Zeile und Quelle ab.
Wäre die .NET Klasse auch als COM-Objekt zu erreichen, könnte man sie auch aus VBScript einfach verwenden:
#### Beispiel für VBScript (COM-Komponente)
' Beispiel:
Set Tail = CreateObject("msxfaq.tail") ' Instanziere Klasse
Tail.filefilter = c:\test\*.log ' definiere überwachtes
Verzeichnis\Datei(en)
Tail.statusstore = filename ' Datei, zur Speicherung des Status
Tail.setfilter ( array of regex) ' Filter für relevante Zeilen
hinterlegen
Tail.savestatus ' Aktuellen Status sichern, d.h. nach erfolgter
Verarbeitung
line = tail.getline (timeout) ' Hole nächste Zeile und
Quelle ab.
Die verschiedenen Methoden sind natürlich nur ein Vorschlag.
Sonstiger Einsatz
Natürlich ist der Einsatz eines "Tail" auf Dateien nur ein erster Ansatz. Es gibt viele anderen "Logs," die man so einfach auswerten kann. Es gibt Tools, die z.B. Eventlogs oder AD-Änderungen in Textdateien schreiben können und damit anderen Diensten zugänglich machen. Dies kostet meist weniger Ressourcen, als bei jedem Aufruf z.B. im Eventlog an die letzte Position zu springen und weiter zu verarbeiten. Hier kann Get-Tail also der Schlüssel für eine weitere effektive Verarbeitung sein.
Weitere Links
- Tail
- Sysinternals
- RegEx
- VBScript
- PowerShell4Admin
- TrackLoginEvents
-
Obtaining Directory Change Notifications
http://msdn.microsoft.com/en-us/library/aa365261(VS.85).aspx -
StreamReader Class
http://msdn.microsoft.com/en-us/library/system.io.streamreader.aspx
Unterstützt "ReadLine" auf einem Stream -
Stream Class
http://msdn.microsoft.com/en-us/library/system.io.stream.aspx
erlaubt das Auslesen und Setzen einer Position -
FileStream Class
http://msdn.microsoft.com/en-us/library/system.io.filestream.aspx -
Writing a Windows PowerShell Cmdlet
http://msdn.microsoft.com/en-us/library/dd878294(v=VS.85).aspx









