Tailing Windows Eventlogs Using TWAPI

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
# 18.10.2013
# (c) Matthias Hoffmann
# Monitor Windows Event Logs

package require twapi_eventlog
package require twapi_console
::twapi::import_commands

proc trapClose {type} {
     global hevl hmon hname signal
     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)}
     }
     set signal 1
}

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
}

# Idlehandler
proc formatOutput {l lbuf} {
     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 {}
     }
}

# der Monitorhandle wird immer als letztes Übergeben (nicht benötigt)
# wird nur gefeuert, wenn tatsächlich ein Signal anliegt
proc eventSink {e l m} {
     after idle [list formatOutput $l [eventlog_read $e]]; # asynchron weitermachen
}

# für eigenes Polling
proc eventPoll {e l} {
     after idle [list formatOutput $l [eventlog_read $e]]; # asynchron weitermachen
     after 5000 [info level 1]; # später Intervall bestimmbar machen
}

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 {
         # zu Beginn die jüngsten 10 Sätze je Log anzeigen
         formatOutput $logname [twapi::eventlog_read $hevl($ix) -seek [expr {[twapi::eventlog_oldest $hevl($ix)]+[twapi::eventlog_count $hevl($ix)]-10}]]
         if {[catch {set hmon($ix) [eventlog_monitor_start $hevl($ix) [list eventSink $hevl($ix) $logname]]}]} {
            # Monitoring auf remote_logs funktioniert nicht, also in diesem Falle eigenes Polling
            eventPoll $hevl($ix) $logname
         }
         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
}

vwait signal
exit 0