Logo: TechTrax...brought to you by MouseTrax Computing Solutions

Synchronized Logging Through Script

by Greg Chapman, MVP (retired)
Skill rating level 9.

You’re the typical admin, always struggling to keep one step ahead of the black hats, always a half dozen steps behind, unable to learn fast enough and being consumed by the fleet of web servers in the collocation farm of a hosting provider 600 miles away. There are times when you would like to follow a problem cascading across that farm from start to finish, but you can’t. It means logging into dozens of machines daily and exporting the logs, bringing them back home over the wire and then settling down for a long day of collating the data to resemble some chronological order of events. By the time you’re ready to take the public bus home, you’ve only just managed to get the data organized. You take the data home on a CD and spend the evening parsing all that data, aware in the back of your mind that tomorrow you’ll probably start the entire process all over again with no real chance of actually learning anything. You are a slave to the machines. The complaints of your wife about the time you spend working (not with her) are merely more scratches in the broken record that has become your life…and, somehow, you’re supposed to do the maintenance on these systems, enforce the system policies and identify when those policies have been abused or the systems breached.

I used to live like this, but that’s really unimportant. What is important is that you don’t have to. Most of the TechTrax musings I’ve produced have been about using script and the Windows Management Instrumentation (WMI) package to automate and organize your administrative duties. And this month, I’ll provide another Windows Script Host variation which will allow you to set up asynchronous event log tracking across a large number of hosts, consolidating all the logged data in a single, local file. That’ll give you a little more time for making your wife happy.

Amongst the capabilities of WMI is something not generally available to the Windows scripter; event handling. WMI allows you to create Event Sinks which can most easily be described as triggers on a trap. When something happens that is handled by that trap, the event is ‘sunk’ and the event handler’s embedded code is kicked into action, responding to the event. What is so wonderful about this (and something that confuses those who think in terms of linear processes) is that time is no longer an issue nor a valid criterion for activation of a process.

The WMI SDK does not tell a very efficient story about creating these event sinks and interpreting them from the documentation is a bit of a black art, in my eyes. Therefore, I won’t spend a lot of time attempting to translate how they work. Instead, this script will use an almost unmodified example sink from the SDK to allow us to monitor all those server event logs.

We’re also going to dabble a little in the world of WSF files. WSF files provide a number of wonderful advantages in Windows Script Host. You can set references to other scripts as an include, create more portable code segments, create several different externally referenced ‘jobs’ and, my favorite, process with more than one script language at a time. For this script, we want everything logged including errors. But we want errors written to a separate file and, this is where JScript comes in, we want a new log every day. For consistency, we’ll set our ‘new day’ reference to be midnight GMT. With that reference, we’ll also be able to monitor servers residing in different time zones and have a chronology that will read the same no matter when we choose to look at the logs. Jscript has a beautiful little set of functions built in for calculating GMT based on your system’s time zone and current time. There’s no need to write our own massive time synchronizer as a result because the WSF file will allow us to process with more than one language.

Process flow will run like this:

  1. Determine current path and the logpath
  2. Get the current time UTC
  3. Ensure we’re processing the script using CScript.exe as our script host
    1. If not, shell out to a new instance of the script using CScript.exe
    2. Kill the original script running in WScript.exe
  4. Look for the list of servers to monitor, EventHosts.txt, in the same directory with the script
  5. Create an Event Sink for each server in the list
  6. Be prepared to gather all the available data about the event and the server it came from using a reference to the computer and theWin32_NTLogEvent Provider and log the data in the log file
  7. Loop infinitely using the wscript.sleep method
  8. In the loop, wake up every 3 seconds and determine the current time. This way, we can anticipate the change of date at midnight and start a new log file
  9. Log any errors to the error log

If you’d like to see how the script is organized at runtime, run it in the script debugger. The script debugger is free from Microsoft and available at http://msdn.microsoft.com/scripting. However, if you’ve already got Microsoft Office XP or newer installed, don’t install the script debugger. The script debugger that comes with Office is much nicer in many ways. To run the script in either debugger, open a command console (CMD.EXE), navigate to the folder containing the script and pass it the debug switches as below:

Cscript <drive>:\<path>\Win32EventRealTime.wsf //D //X

Step through the code and note that once started, it stays in memory, constantly running economically and catches all Event Log calls for all the servers you specified in EventHosts.txt. As it runs, you should see at least one log file being updated in the directory with the script and its contents should match the entries in your system event logs. There is a caveat, though!! The script will only record log events for hosts other than the local machine so make sure you test using another system on the network as your monitoring target. An easy test of the script can be had by using Terminal Services to log into the remote host (which should create log entries as it tries to import your printers) or to simply add a printer to the remote server.

Have fun

Go up to the top of this page.
<?XML version="1.0"?>

<package> 
  <job id="Default">
   <script language="jscript">
    <![CDATA[
     function GetUTCInfo() {
     var d, s;
     d = new Date();
     s = d.getUTCFullYear()+"";
     s += PadZero(d.getUTCMonth()+1)+"" ;
     s += PadZero(d.getUTCDate())+"";
     s += PadZero(d.getUTCHours())+ "";
     s += PadZero(d.getUTCMinutes())+ "";
     s += PadZero(d.getUTCSeconds())+ "";
     return(s)
     }

     function PadZero(intDay) {
     if (intDay < 10)
       return (0 + "" + intDay);
     else
       return (intDay);
     }
   
  </script>
  <script language="VBScript">
   <![CDATA[

    Dim strComputerName,objWshNetwork,strDateStamp, arrHosts, i
    Dim strOpPath, cstrQ, cstrQCQ, s, Counter, intTime
    
    CONST ForReading = 1, ForWriting = 2, ForAppending = 8 

    '********************************************************************
    'Change the values in this section to reflect your local           '*
    'environment.                                                      '*
    '********************************************************************
    CONST SLAHosts = "EventHosts.txt"                                  '*
    CONST ErrLog = "Error.log"                                         '*
    CONST ScriptLog = "EventLog.log"                                   '*
    '********************************************************************

    Public LogFile
    Public ErrorFile
    Public objFSO

    strOpPath = ExecutingFrom
    CheckScriptHost
    s=GetUTCInfo(s)
    Counter=0  
    'This is thousandths of a second
    intTime=3000

    Set objFSO = CreateObject("scripting.FileSystemObject")

    arrHosts = HostsToMonitor (strOpPath & SLAHosts,";")

    For i=0 to UBound(arrHosts)
      'What time is it now? Recalc the value of s
      s=GetUTCInfo(s)
      'You get a unique error file for each UTC day
      ErrorFile = strOpPath & Left(s,8) & ErrLog

      If strOpPath <> "" Then
        LogFile = strOpPath & Left(s,8) & ScriptLog
      Else
        LogFile = strOpPath & Left(s,8) & ScriptLog
      End if

      If Not IsEmpty(arrHosts(i)) Then
        wscript.echo arrHosts(i) & " " & Left(s,12)
        LogAction "Monitoring " & arrHosts(i)
        AsynchEvents(arrHosts(i))
      End If
     Next
  
     strHeader = "[ Date & Time ] --, Record Number, LogFile, " & _
     "Event Identifier, Event Code, Source Name, Type, Category, " & _
     "User, ComputerName, Message" ', Insertion Strings, Data"
     LogAction strHeader

     'wait for however long intTime is before starting this loop
     'again
     'Counter is a variable that will not be set to 1
     'thus we loop until physically interrupted (Ctrl-C)
     Do Until Counter=1
       wscript.sleep(intTime)
       s=GetUTCInfo(s)
       If strOpPath <> "" Then
         LogFile = strOpPath & Left(s,8) & ScriptLog
       Else
         LogFile = strOpPath & Left(s,8) & ScriptLog
       End if
       If NOT objFSO.FileExists(LogFile) Then
         LogAction(strHeader)
       End If
       If Counter=1 Then
         Exit Do
       End if
     Loop
     '====================================================================
     Sub SINK_OnObjectReady(objObject, objAsyncContext)
     WScript.Echo (objObject.TargetInstance.ComputerName) & ", " & _
	          (objObject.TargetInstance.Message)

     strLogEntry=(objObject.TargetInstance.RecordNumber) & ", " & _
     (objObject.TargetInstance.LogFile) & ", " & _
     (objObject.TargetInstance.EventIdentifier) & ", " & _
     (objObject.TargetInstance.EventCode) & ", " & _
     (objObject.TargetInstance.SourceName) & ", " & _
     (objObject.TargetInstance.Type) & ", " & _
     (objObject.TargetInstance.Category) & ", " & _
     (objObject.TargetInstance.User) & ", " & _
     (objObject.TargetInstance.ComputerName) & ", " & _
     (objObject.TargetInstance.Message)
     LogAction strLogEntry
     End Sub
     '====================================================================
     Sub AsynchEvents(strComputer)
  
     On Error Resume Next

     Set services = GetObject("WinMgmts:{impersonationLevel=impersonate, (security)}!\\" & _ 
	 strComputer) 
     If Err <> 0 Then
       LogAction "Setting Remote Event Log Monitoring on " & strComputer & _
       " failed. Error Number: " & Err.Number & ", Error Description: " & _
       Err.Description
       Err.Clear
       Exit Sub
     End If
     Set sink = WScript.CreateObject("WbemScripting.SWbemSink","SINK_")
     services.ExecNotificationQueryAsync sink, _
     "select * from __instancecreationevent where targetinstance isa 'Win32_NTLogEvent'"
  
     On Error GoTo 0

     End Sub
     '====================================================================
     Sub LogAction (strEntry)

     Dim strErrMsg, f

     On Error Resume Next

     set f = objFSO.OpenTextFile(LogFile, ForAppending, True, -2)

     f.WriteLine "[" & Now() & "] -- " & strEntry
     If Err <> 0 Then
       'write to error log
       set f = objFSO.OpenTextFile(ErrorFile, ForAppending, True, -2)
       strErrMsg="Could not write to LogFile. " & Err.Number & " " & _
       err.Description & ". Attempting to write: " & strEntry
       f.WriteLine strErrMsg
     End if
     wscript.echo "Error Occurred: " & strErrMsg
     err.clear
     End If

     f.close

     On Error Goto 0

     End Sub
     '====================================================================
     Function SurveyHost()

     Dim strComputer, objWshNetwork
  
     If(WScript.Arguments.Count) Then
       strComputer = WScript.Arguments.Item(0)
     Else
       Set objWshNetwork = WScript.CreateObject("WScript.Network")
       strComputer = objWshNetwork.ComputerName
       Set objWshNetwork = Nothing
     End If

     SurveyHost=strComputer

     End Function
     '====================================================================
     Function ErrorLogName()
  
     ErrorLogname=ExecutingFrom & Left(s,8) & ErrLog

     End Function
     '=======================================================
     Function HostsToMonitor(HostSource, strComment)

     Dim ts,tsLine,arrLines
  
     On Error Resume Next

     Redim arrLines(0)

     Set ts=objFSO.OpenTextFile(HostSource,ForReading,False)
     If Err <> 0 Then
       'SurveyHost function identifies host to monitor
       arrLines(0)=SurveyHost
       HostsToMonitor=arrLines
       Err.Clear
       Exit Function
     End If

     Do While NOT ts.AtEndOfStream 
       tsLine = Trim(ts.ReadLine)
       If tsLine <> "" AND (Left(tsLine,1) <> strComment) Then
        Boundary=UBound(arrLines)
        Redim Preserve arrLines(Boundary +1)
        arrLines(Boundary)=tsLine
       End If
     Loop
     ts.Close

     HostsToMonitor=arrLines

     End Function
     '=======================================================
     Function ExecutingFrom()

     Dim strScriptPath

     strScriptPath=Left(wscript.scriptfullname, _
     Len(wscript.scriptfullname)-Len(wscript.scriptname))
  
     If Right(strScriptPath,1) <> "\" Then
       strScriptPath=strScriptPath & "\"
     End If

     ExecutingFrom=strScriptPath

     End Function
     '====================================================================
     Sub CheckScriptHost()

     If InStr(LCase(wscript.fullname),"cscript") = 0 Then
     strMsg = "Script must be run by CScript.exe. Terminating this " & _
     "script, changing the default script engine and restarting" & _
     " execution."
     Dim objShell
     Set objShell=CreateObject("wscript.shell")
     strExec = "cscript.exe //NoLogo //H:cscript //S"
     objShell.Run strExec,0,TRUE
         
     strExec ="cscript.exe " & Chr(34) & _
     wscript.scriptfullname & Chr(34)'& " " & _
     '		  Chr(34) & strArg & Chr(34) & Chr(34)
        
     objShell.Run strExec,,False
     Wscript.Quit
     End If

     End Sub
     '====================================================================
   ]]>
  </script>
 </job>
</package>

 

Go up to the top of this page.
This site powered by the Logical Web Publisher™: Content management by Logical Expressions, Inc.