Version 2 of Tailing Windows Eventlogs Using TWAPI

Updated 2013-03-06 10:45:44 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] {
        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 0]
      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
}