Tuesday, April 20, 2010

Audit Windows 2003 print server usage


This post provides information on a solution to provide audit logs and summary information on printer usage on a Windows print server. This will provide daily and monthly printer event logs, and summary results based on one or more log files.

Pre-requisites:

  • A scheduled task that runs every day to collect and process the logs, it doesn’t have to be on the print server.
  • 'Log Spooler Information Events' on the printer spooler in question, which will write Event ID 10 entries every time someone prints through the spooler
  • A system event log big enough to capture at least one day's logs

Create a batch file that contains the following commands. Note that you will need to modify the variables or to reference paths as appropriate, eg for dumpel.exe and the VBScript, and the print/log dir.

Set PrintDir=c:\Print
Set LogDir=C:\logs
for /f "tokens=1-8 delims=/:. " %%i in ('echo %date%') do Set DateFlat=%%l%%k%%j
dumpel -s \\%print_server% -l System -e 10 -m Print -d 1 >> %logDir%\%server%_jobs_%DateFlat%.csv
for /f "tokens=3,4 delims=/ " %%i in ('echo %date%') do copy %server%_jobs_%%j%%i??.csv %PrintDir%\PrintJobs_%%j%%i.csv /y
cscript ProcessPrinterLogs.wsf /f:%LogDir%


If you create a scheduled task to run this batch file every day at the same time, these commands will:

  1. Dump the event logs for the past day to a daily log file with YYYYMMDD suffix.
  2. Collate the daily log files into a monthly log file, by appending each daily file. For each day in a month, this command will overwrite the previous monthly log, until it runs on the last day of the month.
  3. The script processes the dumpel log entries, providing different views in the form of per-printer, per-user, per-day totals of jobs/pages/bytes (useful for graphics), as well as summary totals of the information.

This has the following advantages:

  • It’s simple and not very intensive. A SQL database with a recurring DTS job to import the logs and then using SQL Reporting Services would be a lot prettier, but really not that much more functional or useful.
  • You will have a permanent set of log files, one for each month that you can store for historical purposes, while purging the daily log file directory every so often.

The ProcessPrinterLogs.vbs script that does all the work is listed below. To run:
cscript ProcessPrinterLogs.vbs /f:%logDir%

  Const ForReading = 1, ForWriting = 2, ForAppending = 8  Set objFSO = CreateObject("Scripting.FileSystemObject") Set objShell = CreateObject("WScript.Shell")  Main()   Sub Main()     If WScript.Arguments.Named.Exists("f") Then         sSource = Wscript.Arguments.Named("f")     Else         Wscript.Arguments.ShowUsage()         Wscript.Echo "Source file or directory must be supplied"         Wscript.Quit(2)     End If      If Wscript.Arguments.Named.Exists("o") Then         sOutputFile = Wscript.Arguments.Named("o")     Else         dNow = Now             dLogDate = DatePart("yyyy", dNow)          dLogDate = dLogDate & String(2 - Len(DatePart("m", dNow)),"0") & DatePart("m", dNow)         dLogDate = dLogDate & String(2 - Len(DatePart("d", dNow)),"0") & DatePart("d", dNow)             sOutputFile = objShell.ExpandEnvironmentStrings("%Temp%")         sOutputFile = sOutputFile & "\" & Left(WScript.ScriptName, InStrRev(WScript.ScriptName,".vbs")-1) & "_" & dLogDate & ".csv"     End If      wscript.echo "Input file/dir: '" & sSource & "'"     wscript.echo "Output file: '" & sOutputFile & "'"       If objFSO.FileExists(sSource) Then          sFileSet = sSource                                        ' Process a single file         wscript.echo "Single file specified - " & sFileSet     ElseIf objFSO.FolderExists(sSource) Then         wscript.echo "Source specified was a directory, reading files from '" & sSource & "'"         sFileSet = ""         Set oFolder = objFSO.GetFolder(sSource)                                ' Get the folder         Set oFiles = oFolder.Files         For Each oFile in oFiles                                    ' For each file             sFileset = sFileset & vbCRLF & oFile.Path                         ' Append to the fileset         Next         If Len(sFileSet) > Len(vbCRLF) Then sFileSet = Right(sFileSet, Len(sFileSet) - Len(vbCRLF))    ' Trim the leading CRLF     End If      Set dPrinters  = CreateObject("Scripting.Dictionary")                            ' Create the dictionary objects     Set dusers = CreateObject("Scripting.Dictionary")     Set dDates = CreateObject("Scripting.Dictionary")     Set dJobs = CreateObject("Scripting.Dictionary")      For Each sFile in Split(sFileset, vbCRLF)                                ' For Each file         wscript.echo "Processing '" & sFile & "'"         sBuffer = ""            Set objTextStream = objFSO.OpenTextFile(sFile, ForReading)               sBuffer = objTextStream.ReadAll          For Each sLine in Split(sBuffer, vbCRLF)                            ' For each line in this file             Call ProcessLogEntry(sLine, dPrinters, dUsers, dDates, dJobs)                ' Process the log entry         Next     Next      Call ProduceOutput(sOutput, dPrinters, dUsers, dDates, dJobs)                        ' Produce the output     Set objTextStream = objFSO.OpenTextFile(sOutputFile, ForWriting, True)     objTextStream.Write sOutput     wscript.echo "Output saved to '" & sOutputFile & "', " & Len(sOutput) & " characters."  End Sub  Function ProduceOutput(ByRef sOutput, ByRef dPrinters, ByRef dUsers, ByRef dDates, ByRef dJobs)     Dim strPrinter, strPort, dtmDate, strUser, strserver, strDocumentName, intSize, intPages, strInformation, strTotal     Dim strUserTotal, strPrinterTotal, strDateTotal, strJobTotal, aJobTotal      sOutput = ""     For Each strPrinter in dPrinters.Keys                 sOutput = sOutput & vbCRLF & strPrinter & "," & dPrinters.Item(strPrinter)     Next      sOutput = sOutput & vbCRLF     For Each strUser in dUsers.Keys         sOutput = sOutput & vbCRLF & strUser & "," & dUsers.Item(strUser)     Next      sOutput = sOutput & vbCRLF     For Each dtmDate in dDates.Keys         sOutput = sOutput & vbCRLF & dtmDate & "," & dDates.Item(dtmDate)     Next      sOutput = sOutput & vbCRLF     For Each strTotal in dJobs.Keys         strJobTotal = dJobs.Item(strTotal)         aJobTotal = Split(strJobTotal, ",")         sOutput = sOutput & vbCRLF & "Total Jobs," & aJobTotal(0)         sOutput = sOutput & vbCRLF & "Total Pages," & aJobTotal(1)         sOutput = sOutput & vbCRLF & "Total Size (MB)," & aJobTotal(2)     Next      sOutput = sOutput & vbCRLF     strUserTotal = UBound(dUsers.Keys)+1     strPrinterTotal = UBound(dPrinters.Keys)+1     strDateTotal = UBound(dDates.Keys)+1     sOutput = sOutput & vbCRLF & "Printers," & strPrinterTotal      sOutput = sOutput & vbCRLF & "Users," & strUserTotal      sOutput = sOutput & vbCRLF & "Days," & strDateTotal       aJobTotal = Split(strJobTotal, ",")     sOutput = sOutput & vbCRLF      sOutput = sOutput & vbCRLF & "Average jobs/person," & CInt(aJobTotal(0)/strUserTotal)     sOutput = sOutput & vbCRLF & "Average pages/person," & CInt(aJobTotal(1)/strUserTotal)     sOutput = sOutput & vbCRLF & "Average pages/person/day," & CInt(CInt(aJobTotal(1)/strUserTotal) / strDateTotal)     sOutput = sOutput & vbCRLF & "Average pages/minute," & CInt(aJobTotal(1) / (strDateTotal * 8 * 60))  End Function  Function ProcessLogEntry(ByRef sLine, ByRef dPrinters, ByRef dUsers, ByRef dDates, ByRef dJobs)     Dim strPrinter, strPort, dtmDate, strUser, strserver, strDocumentName, intSize, intPages, strInformation      Dim aPrintJob, intOffset, strTemp, aTemp      aPrintJob = Split(sLine, vbTAB)       If UBound(aPrintJob) = 9 Then         dtmDate = aPrintJob(0) ' & " " & aPrintJob(1)         aTemp = Split(dtmDate, "/")         dtmDate = Right("00" & Trim(aTemp(1)), 2) & "/" & Right("00" & Trim(aTemp(0)), 2) & "/" & aTemp(2)        ' Trim, pad and switch to dd/mm/yyyy instead of mm/dd/yyyy         strServer = aPrintJob(8)          strInformation = Trim(aPrintJob(9))         strInformation = Right(strInformation, Len(strInformation) - InStr(strInformation, " "))    ' Remove the job ID         intOffset = InStrRev(strInformation, " ")         intPages = Right(strInformation, Len(strInformation) - intOffset)        ' Extract the number of pages from the end         strInformation = Left(strInformation, intOffset-1)                ' Trim the string              intOffset = InStrRev(strInformation, " ")         intSize = Right(strInformation, Len(strInformation) - intOffset)        ' Extract the number of bytes from the end         strInformation = Left(strInformation, intOffset-1)                ' Trim the string                  intOffset = InStrRev(strInformation, " ")         strPort = Right(strInformation, Len(strInformation) - intOffset)        ' Extract the port from the end         strInformation = Left(strInformation, intOffset-1)                ' Trim the string                  intOffset = InStrRev(strInformation, " ")         strPrinter = Right(strInformation, Len(strInformation) - intOffset)        ' Extract the printer from the end         strInformation = Left(strInformation, intOffset-1)                ' Trim the string                  intOffset = InStrRev(strInformation, " ")         strUser = Right(strInformation, Len(strInformation) - intOffset)        ' Extract the user from the end         strInformation = Left(strInformation, intOffset-1)                ' Trim the string                  strDocumentName = strInformation          If dPrinters.Exists(strPrinter) Then                         ' Does this printer already exist in the dictionary?             aTemp = Split(dPrinters.Item(strPrinter), ",")                ' Find the existing printer job/page count             aTemp(0) = aTemp(0) + 1                            ' Increment the job count             aTemp(1) = aTemp(1) + CInt(intPages)                    ' Add to the page count             aTemp(2) = aTemp(2) + CInt(intSize/1024/1024)                ' Add to the byte count             dPrinters.Item(strPrinter) = Join(aTemp, ",")                ' Update the dictionary         Else             aTemp = Array(1, intPages, CInt(intsize /1024/1024))            ' Start the job/page count             dPrinters.Add strPrinter, Join(aTemp, ",")                ' Create this item         End If              If dUsers.Exists(strUser) Then                             ' Does this user already exist in the dictionary?             aTemp = Split(dUsers.Item(strUser), ",")                ' Find the existing user job/page count             aTemp(0) = aTemp(0) + 1                            ' Increment the job count             aTemp(1) = aTemp(1) + CInt(intPages)                    ' Add to the page count             aTemp(2) = aTemp(2) + CInt(intSize/1024/1024)                ' Add to the byte count             dUsers.Item(strUser) = Join(aTemp, ",")                    ' Update the dictionary         Else             aTemp = Array(1, intPages, CInt(intsize /1024/1024))            ' Start the job/page count             dUsers.Add strUser, Join(aTemp, ",")                    ' Create this item         End If          If dDates.Exists(dtmDate) Then                             ' Does this date already exist in the dictionary?             aTemp = Split(dDates.Item(dtmDate), ",")                ' Find the existing date job/page count             aTemp(0) = aTemp(0) + 1                            ' Increment the job count             aTemp(1) = aTemp(1) + CInt(intPages)                    ' Add to the page count             aTemp(2) = aTemp(2) + CInt(intSize/1024/1024)                ' Add to the byte count             dDates.Item(dtmDate) = Join(aTemp, ",")                    ' Update the dictionary         Else             aTemp = Array(1, intPages, CInt(intsize /1024/1024))            ' Start the job/page count             dDates.Add dtmDate, Join(aTemp, ",")                    ' Create this item         End If          If dJobs.Exists(JOB_TOTAL) Then                         ' Does the total already exist in the dictionary?             aTemp = Split(dJobs.Item(JOB_TOTAL), ",")                ' Find the existing total counts             aTemp(0) = aTemp(0) + 1                            ' Increment the job count             aTemp(1) = aTemp(1) + CInt(intPages)                    ' Add to the page count             aTemp(2) = aTemp(2) + CInt(intSize/1024/1024)                ' Add to the byte count             dJobs.Item(JOB_TOTAL) = Join(aTemp, ",")                ' Update the dictionary         Else             aTemp = Array(1, intPages, CInt(intsize /1024/1024))            ' Start the job/page count             dJobs.Add JOB_TOTAL, Join(aTemp, ",")                    ' Create this item         End If     Else         wscript.echo "skipped '" & sLine & "'"     End If End Function

No comments:

Post a Comment