1

I'm working on Windows Server 2008 R2 and I want to create a simple script to audit the printing carried out in the company.

I would like these details to be audited in an text file in a share and for the script to be run after someone sends something to print:

"user name, computer name, document name and path, number of pages, printer name, date, time"

Any suggestions would be greatly appreciated.

Thank you.

  • 1
    **Printer auditing is a nightmare**. It just isn't possible to do all that via a simple script. Print driver simply doesn't give you all of that information. You probably should be looking at leasing/buying a multi-function printer that has the auditing feature on the printer. – Zoredache Oct 07 '13 at 22:12
  • Hi Zoredache, if a simple script is not the way to go then how can I achieve this by using a complex script? What is the code that I would require? Any suggestions? – Computeristic Oct 07 '13 at 22:18
  • We use [PaperCut Print Logger](http://www.papercut.com/products/free_software/print_logger/), free as in beer, it works pretty well. It generates HTML and CSV logs. With a little scripting-saavy, you should be able to import the CSVs into your reporting DB of choice. The vendor also offers pay versions which offer more features. – jscott Oct 08 '13 at 00:08
  • Actually I have a script that does pretty much what you are asking as long as the server itself is the "print server" and you aren't using direct IP printing. I'll have to grab it in the morning though, but I'll post it back here. It creates those audit logs as well as a spreadsheet that consolidates the logs into reports. – TheCleaner Oct 08 '13 at 00:57
  • @jscott - hmmm...I think I like that product better than my script...but I'll still post it if the OP likes. – TheCleaner Oct 08 '13 at 00:58
  • @TheCleaner Please, do post. – jscott Oct 08 '13 at 01:06
  • Jscott Thank you for your suggestion. I'll check this product out. And The Cleaner please do post your script here. It sounds very useful. Also if you can then please comment Your script so that I know what each line does. Is it in VB or cmd? – Computeristic Oct 08 '13 at 08:49

1 Answers1

2

Folks here's the script(s) I used in a former life. This was on a 2003 print server, but I'd presume them to work fine in 2008 as well. I got most of this online from someone's blog post but I don't recall where...it was years ago.

How it works:

auditprinters.bat = batch file (scheduled daily) that does the following:

  1. Parses through input via FOR loop of print servers. In my case I only had a single print server but if you had additional you could modify the FOR loop to allow for an input file, etc.
  2. Sets up various output folders
  3. Deletes logs older than 30 days
  4. Dumps the printer logs using DUMPEL (make sure you have the Windows Resource Kit installed or get dumpel and place it somewhere and modify that line of the batch file)
  5. Makes a backup of the current logs
  6. Process the logs calling processprinterlogs.vbs

ProcessPrinterLogs.vbs = script that parses through the printer logs (formatting, etc.)

I then used Excel to open the files and do my own sorting, manipulation as necessary.


CODE:

auditprinters.bat

For  %%S IN (PRINTSERVER.MDMARRA.LOCAL) DO (

Set print_server=%%S
Call :s_get_logs

)
Goto eof

:s_get_logs
Set MainDir=C:\PrinterLogs\%print_server%
Set PrintDir=%MainDir%\printdir
Set LogDir=%MainDir%\logs

:: Delete log files older than 30 days
forfiles /p %LogDir% /d -30 /c "CMD /C del @FILE"

:: Create needed directories
If not Exist %PrintDir% MD %PrintDir%
If not Exist %LogDir% MD %LogDir%

:: Set date format
For /f "tokens=1-8 delims=/:. " %%i IN ('echo %date%') do Set DateFlat=%%j%%k%%l

:: Set log an backup files
Set LogFile=%print_server%_jobs_%DateFlat%.csv
Set BackFile=PrintJobs_%DateFlat%.csv

:: Dump the printer log
:: Using full path for safety
"C:\Program Files\Resource Kit\dumpel" -s \\%print_server% -l System -e 10 -m Print -d 1 >> %logDir%\%LogFile%

:: Make a backup copy
Copy %logDir%\%print_server%_jobs_%DateFlat%.csv %PrintDir%\%BackFile% /y

:: Process the logs
cscript ProcessPrinterLogs.vbs /f:%LogDir% /o:%MainDir%\%print_server%_auditoutput.csv
:eof

ProcessPrinterLogs.vbs

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 = 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 dUsersPerPrinter = CreateObject("Scripting.Dictionary")
    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

        Set objFile = objFSO.GetFile(sFile)       
        If objFile.size > 0 Then           ' Don't process the file if it is a 0 byte 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, dUsersPerPrinter)                ' Process the log entry
            Next
        End If
    Next


    Call ProduceOutput(sOutput, dPrinters, dUsers, dDates, dJobs, dUsersPerPrinter)                        ' 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, ByRef dUsersPerPrinter)
    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

    'new portion to output list of users per print queue
    sOutPut = sOutput & vbCRLF
    For Each strPrinter In dUsersPerPrinter.Keys
        sOutput = sOutput & vbCRLF & strPrinter & "," & dUsersPerPrinter.Item(strPrinter)
    Next
    'end of new output

    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, ByRef dUsersPerPrinter)
    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

        ' This section creates a list of users that are using each print queue
        If dUsersPerPrinter.Exists(strPrinter) Then      ' Does the printer exist as a key in the dictionary?
            Dim bTemp
            bTemp = dUsersPerPrinter.Item(strPrinter) & "," & strUser ' build up the list of users
            dUsersPerPrinter.Item(strPrinter) = DedupeString(bTemp, ",") ' dedupe sting of users and populate dictionary
        Else
            dUsersPerPrinter.Add strPrinter, strUser     ' Create this item
        End If
        ' End of user list creation

    Else
        WScript.echo "skipped '" & sLine & "'"       ' line skipped because number of elemnts in array not equal to 9 ( need to figure this one out)
    End If
End Function


' Deduping  a  string
' must use this function so we end up with a unique list of users with no duplicates
Function DedupeString(inString, strSeperate)
    Dim vObjects, myDict, index, strFinal
    strFinal = ""
    Set myDict = CreateObject("Scripting.Dictionary")
    vObjects = Split(inString, strSeperate)
    For index = 0 To UBound(vObjects)
        If (Not myDict.Exists(vObjects(index))) Then
            myDict.Add vObjects(index), vObjects(index)
            If (Len(strFinal)) > 0 Then
                strFinal = strFinal & strSeperate & myDict(vObjects(index))
            Else
                strFinal = myDict(vObjects(index))
            End If
        End If
    Next
    DedupeString = strFinal
End Function
TheCleaner
  • 32,627
  • 26
  • 132
  • 191
  • There should be a super downvote for posting .VBS and .BAT – Dan Oct 09 '13 at 13:24
  • 2
    @Dan - hey now. This is probably at least 6 to 7 years old! I had to dig it out of my cloud archives! – TheCleaner Oct 09 '13 at 13:29
  • @TheCleaner Thank you very much for your script. But now I have decided not to go ahead with the printing audit as my boss doesn't need it set up now. – Computeristic Oct 21 '13 at 23:48