Version 1 of Tailing Windows Eventlogs Using TWAPI

Updated 2013-03-06 10:05:21 by MHo

MHo: Program for tailing ms windows event logs using twapi. Work in progress. use it in any way you want, but at your own risk. This is the main prog of a starpack which will later be made downloadable somewhere.

   package provide app-wineltail 1.0

   # wineltail
   # 06.03.2013
   # (c) Matthias Hoffmann
   # Monitor Windows Event Logs

   package require twapi
   ::twapi::import_commands

   proc trapClose {type} {
        global hevl hmon hname
        puts stderr $type
        foreach h [array names hmon] {
           puts stderr "Stoppe Monitor Instanz $hmon($h) für $hname($h)"
           catch {eventlog_monitor_stop $hmon($h)}
        }
        foreach h [array names hevl] {
           # später die Namen der Logs anzeigen
           puts stderr "Schliesse Eventloginstanz $hevl($h) für $hname($h)"
           catch {eventlog_close $hevl($h)}
        }
        exit 0
   }

   proc formatSID {sid} {
        if {[string length $sid]} {
           if {![catch {lookup_account_sid $sid} rc]} {
              return $rc
           }
        }
        return [string range $sid end-6 end]
   }

   proc formatEventID {eid} {
        # diese merkwürdige Logik muss auch noch gecheckt werden!
        set e [expr {$eid-1073741824}]
        if {$e < 0} {
           set e 1
        }
        return $e
   }

   proc formatOutput {} {
        foreach {l lbuf} $::buffer {
           foreach eventrec $lbuf {
              array set event $eventrec
              # $event(-sid) später nur optional!! Bzw. Ausgabe gemäss variabler Schablone.
              # Verwendung von lookup_account_sid direkt in eventSink führt zum Verschlucken von Events,
              # daher hier asynchron
              # [list [string range $event(-sid) end-6 end]]
              puts "[list [clock format $event(-timegenerated) -format {%Y%m%d %T}]] \
                    [list $event(-system)] \
                    [list [formatSID $event(-sid)]] \
                    [list $l] \
                    [list $event(-recordnum)] \
                    [list [string range $event(-type) 0 6]] \
                    [list $event(-source)] \
                    [list [eventlog_format_category $eventrec -width -1]] \
                    [list [formatEventID $event(-eventid)]] \
                    [list [eventlog_format_message $eventrec -width -1]]"
              array set event {}
           }
        }
        set ::buffer [list]
   }

   # der Monitorhandle wird immer als letztes Übergeben (nicht benötigt)
   # wird nur gefeuert, wenn tatsächlich ein Signal anliegt
   proc eventSink {e l m} {
        while {[llength [set events [eventlog_read $e]]]} {
           # Events asynchron weiterverarbeiten
           lappend ::buffer $l $events
        }
        set ::signal 1
   }

   # für eigenes Polling
   proc eventPoll {e l} {
        set foundNew 0
        while {[llength [set events [eventlog_read $e]]]} {
           # Events asynchron weiterverarbeiten
           lappend ::buffer $l $events
           incr foundNew
        }
        if {$foundNew > 0} {
           set ::signal 1
        }
        after 5000 [info level 1]; # sich selbst erneut aufrufen
   }

   if {$argc == 0} {
      puts stderr {Parameter: (engl.) Name(n) der zu überwachenden Eventquelle(n), Bsp.: System
   Es können mehrere Quellen angegeben werden, Bsp.: System Application
   Es kann je Quelle ein Systemname angegeben werden, Bsp.: "System Matthias" Application
    (in diesem Fall wird versucht, die System-Events vom remote Rechner Matthias zu lesen,
     und Application-Events vom lokalen Rechner).
   Das Auflösen von Benutzernamen funktioniert ggF. nicht. Es werden dann die letzten Stellen
   der SID zurückgegeben.
   Das Programm kann mit Strg+C beendet werden.
   }
      exit 255
   }

   set_console_control_handler [list trapClose]

   set ix 0
   array set hevl {}
   array set hmon {}
   array set hname {}
   foreach log $argv {
      if {[llength $log] == 2} {
         set logname [lindex $log end]
         set logsys  [lindex $log 1]
      } else {
         set logname $log
         set logsys  $::env(computername)
      }
      if {![catch {set hevl($ix) [eventlog_open -source $logname -system $logsys]} rc]} {
         if {[catch {
            # RecordPointer zum Ende (es gibt kein SEEK...), aber gelesenes nicht anzeigen
            while {[llength [eventlog_read $hevl($ix)]]} {}
            if {[catch {set hmon($ix) [eventlog_monitor_start $hevl($ix) [list eventSink $hevl($ix) $logname]]}]} {
               # Monitoring auf remote_logs soll nicht funktionieren; also hier eigenes Polling verwenden (ungetestet)
               after 5000 [list eventPoll $hevl($ix) $logname]; # später Intervall bestimmbar machen
            }
            set hname($ix) "$logsys $logname"; # nur für Anzeige beim späteren Schließen
            incr ix
         }  rc]} {
            puts stderr "(2) $rc"
         }
      } else {
         puts stderr "(1) $rc"; # im Heimnetz nicht testbar!? ("der RPC-Server ist nicht verfpgbar")
      }
   }
   if {$ix == 0} {
      puts stderr "Es konnten keine Logs geöffnet werden!"
      exit 1
   }

   set buffer [list]
   set signal 0
   while 1 {
      vwait signal
      formatOutput
   }