At my company, we have a number of sites - some big, some small. Each site has a "site server", at the small sites, each server does multiple things, but at the larger sites, we have multiple servers to handle each of the functions. One of those functions is printing.
Because time is money, we want to know about a problem before the end user has to take the time and call us to inform us that we have a problem.
Our print servers have the standard monitoring enabled (can we ping the server, is the Spooler service running, is the IIS service running, do we have ample disk space) but that doesn't always tell you when the server is in a sour state. Perhaps 50 huge jobs have come thru at the same time to 50 different printers, and the server is really busy, but is in fact working just fine (so standard monitors won't throw and alert). Users would start calling saying nothing is printing, or jobs are taking 'forever' to print, but everything you look at tells you things should be working just fine.
I looked high and low for Perfmon counters that would give me some more detail, but nothing jumped out. Even the counters for the Spooler service don't tell you much. So I wrote a script.
You setup a printer on each server called PrintToFile - any job sent to that printer does just that, prints to a file. The script will send a job to that printer, then use the job's output to determine just how long it took to print.
This script also demonstrates some interesting things, such as:
-Quickly PING a server (via WMI) to see if it will respond (does not use ping.exe)
-Connect to the print server and validate the Spooler service is running
-Print a test page to a remote printer via printui.dll (originally I was using WMI, however with servers that have many printers (over 200+) enumerating all the printers took way too long and caused the server to work harder than it needed to)
-An 'Alert' process that uses a file to determine previous state. I don't want to be alerted over and over for one problem. I do want to be alerted when the state changes (good -> bad, bad -> good)
-Sending an e-mail
':: PrintSpeedTest.vbs'
':: Assumptions:'
':: This script assumes the following:'
':: -There is a printer on the Print Server named PrintToFile'
':: -The output of the printer is a file named E:\PrintToFile.txt'
':: (if another path is used, stipulate that as a second argument)'
':: -The driver used is a Xerox WorkCentre 245 PS (however, any PS driver will probably do)'
':: Prerequisits:'
':: Setup a printer on print server that will print to a file.'
':: -Launch Add Printer Wizard'
':: -Add a local printer'
':: -Create a new port, Local Port'
':: -Name the port the path of the output file - E:\PrintToFile.txt'
':: -Assign a PS driver to the printer (Xerox WorkCentre245 PS) as PS files are easy to read'
':: The headder of the PS file is used to determine how long the job took to process'
':: Usage:'
':: -Fill out PrintShare, MailTo, MailFrom and TimeoutValue values'
':: -Also fill out the SMTP server info inside the vbSendMail sub'
':: -cscript PrintSpeedTest.vbs <PrintServer> [<Path to Output file>]'
Const PrintShare = "PrintToFile"
Const MailTo = <Alert Email>
Const MailFrom = <From Email>
Const TimeoutValue = 60 'seconds
':: Process Arguments'
':: Only acceptable arguments are 'print server name' and 'path to output file'
':: If 'path to output file' is omitted, it defaults to E$'
Set objArgs = WScript.Arguments
If objArgs.Count < 1 Or objArgs.Count > 2 Then
WScript.Echo "Usage: cscript PrintSpeedTest.vbs <PrintServer> [<Path to Output file>]"
WScript.Quit(0)
End If
PrintServer = UCase(objArgs(0))
If objArgs.Count = 2 Then PathToOutfile = objArgs(1) Else PathToOutfile = "E$"
OutputFile = "\\" & PrintServer & "\" & PathToOutfile & "\PrintToFile.txt"
':: Setup standard variables for this particular instance
ScriptName = WScript.ScriptName
Subject = ScriptName & "-" & PrintServer
fileUP = ScriptName & "-" & PrintServer & "-UP.check"
fileDN = ScriptName & "-" & PrintServer & "-DN.check"
':: Define and setup variables used outside the Main Sub
Dim TestPassed, msg, tTIME
TestPassed = False
msg = ScriptName & vbCrLf
msg = msg & "Print Monitor for " & PrintServer & vbCrLf & vbCrLf
':: Ok, lets get started with some real work
Call Main(PrintServer, PrintShare, OutputFile, TimeoutValue)
':: If the value returned for the total time taken to print the job has a length less than one, substitute '-1'
':: Since this script could also be used to chart total time over time (MRTG), write out this value
If Len(tTIME) < 1 Then tTIME = "-1"
WScript.Echo tTIME
':: Pass results thru Alert. If state changes (Good -> Bad, Bad -> Good) send an alert
':: Otherwise simply update the current state file with current results
':: fileUP or fileDN (located in the same folder as this script) are used to keep track of the current state
Call Alert(TestPassed, fileUP, fileDN, Subject, MailTo, MailFrom, msg)
WScript.Quit
'__________________________________________________________________________________
Sub Main(SERVER, PRINTER, OUTFILE, TIMEOUT)
':: PreTest: Ping Server
If Ping(SERVER) <> True Then
msg = msg & "Unable to ping server " & SERVER & vbCrLf
TestPassed = False
Exit Sub
End If
':: PreTest: Delete previous print output file
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(OUTFILE) Then fso.DeleteFile OUTFILE, True
If fso.FileExists(OUTFILE) Then
msg = msg & "Unable to delete " & OUTFILE & ": " & Err.Description & vbCrLf
TestPassed = False
Err.Clear
Exit Sub
End If
':: PreTest: Connect to WMI
On Error Resume Next
Set objWMI = GetObject("winmgmts:\\" & SERVER)
If Err.Number Then
msg = msg & "Cannot connect to WMI service on " & SERVER & ": " & Err.Description
TestPassed = False
Err.Clear
Exit Sub
End If
On Error Goto 0
':: PreTest: Verify Spooler service is running
strQueryService = "Select * From Win32_Service Where Name = 'Spooler'"
Set colServices = objWMI.ExecQuery(strQueryService)
For Each objService In colServices
If objService.State <> "Running" Then
msg = msg & "Spooler service not running. Current state: " & objService.State & vbCrLf
TestPassed = False
Exit Sub
End If
Next
':: PreTest: Verify we find one and only one printer
Set WshShell = CreateObject("WScript.Shell")
cmdNetView = "cmd /c net view \\" & SERVER & " | findstr " & PRINTER & " "
Set objNetView = WshShell.Exec(cmdNetView)
strNetView = objNetView.StdOut.ReadAll
asdf = Err.Number
If Len(strNetView) = 0 Then
msg = msg & "Printer " & PRINTER & " not found on server " & SERVER & vbCrLf
TestPassed = False
Exit Sub
End If
':: All pre-tests passed! No reason the job shouldn't print at this point.
cmdPrintTest = "cmd /c rundll32 printui.dll,PrintUIEntry /k /n " & Chr(34) & "\\" & SERVER & "\" & PRINTER & Chr(34)
sTIME = Now()
Set objPrintTest = WshShell.Exec(cmdPrintTest)
strPrintTest = objPrintTest.StdOut.ReadAll
':: Print sent, now lets see if it successfully printed
':: Give the system two seconds to catch up
WScript.Sleep 2000
':: Test: Look for Output file
If Not fso.FileExists(OUTFILE) Then
msg = msg & OUTFILE & " not found after sending print job." & vbCrLf
TestPassed = False
Exit Sub
End If
':: Test: Make sure Output file looks like a PostScript print job
If Left(fso.OpenTextFile(OUTFILE).ReadLine, 13) <> "%-12345X@PJL" Then
msg = msg & OUTFILE & " not a valid PostScript file. Missing '%-12345X@PJL' at top" & vbCrLf
TestPassed = False
Exit Sub
End If
':: All tests passed!
':: Determine how long the whole process took
Set fsoOutput = fso.OpenTextFile(OUTFILE)
Do Until fsoOutput.AtEndOfStream
L = fsoOutput.ReadLine
':: Look for the following line: %%CreationDate: 1/1/2010 12:00:00 and derive elapsed time
If Left(L, 16) = "%%CreationDate: " Then
fTIME = CDate(Split(L, " ", 2)(1))
Exit Do
End If
Loop
tTIME = DateDiff("s", sTIME, fTIME)
If tTIME > TIMEOUT Then
msg = msg & "Print job to server " & SERVER & " took more than " & TIMEOUT & " seconds"
TestPassed = False
Exit Sub
End If
msg = msg & "All tests passed." & vbCrLf
msg = msg & "Elapsed Time: " & tTIME & " seconds" & vbCrLf
TestPassed = True
Exit Sub
End Sub
'__________________________________________________________________________________
Sub Alert(CheckState, fileUP, fileDN, Subject, MailTo, MailFrom, msg)
Set fso = CreateObject("Scripting.FileSystemObject")
If CheckState = True Then
If fso.FileExists(fileUP) Then
Set fsoUp = fso.OpenTextFile(fileUP, 8)
fsoUp.WriteLine GetDateTime() & "-StillGood " & "(" & tTIME & " sec)"
fsoUp.Close
Else
If fso.FileExists(fileDN) Then
If fso.FileExists(fileDN & "." & GetDateTime()) Then fso.DeleteFile fileDN & "." & GetDateTime(), True
fso.MoveFile fileDN, fileDN & "." & GetDateTime()
End If
Set fsoUP = fso.OpenTextFile(fileUP, 2, True)
fsoUP.WriteLine msg
fsoUP.WriteLine GetDateTime() & "-Good " & "(" & tTIME & " sec)"
fsoUP.Close
Set fsoMail = fso.OpenTextFile(fileUP, 1)
strMsg = fsoMail.ReadAll
fsoMail.Close
vbSendMail Subject, MailTo, MailFrom, msg
End If
Else
If fso.FileExists(fileDN) Then
Set fsoUp = fso.OpenTextFile(fileDN, 8)
fsoUp.WriteLine GetDateTime() & "-StillBad " & "(" & tTIME & " sec)"
fsoUp.Close
Else
If fso.FileExists(fileUP) Then
If fso.FileExists(fileUP & "." & GetDateTime()) Then fso.DeleteFile fileUP & "." & GetDateTime(), True
fso.MoveFile fileUP, fileUP & "." & GetDateTime()
End If
Set fsoDN = fso.OpenTextFile(fileDN, 2, True)
fsoDN.WriteLine msg
fsoDN.WriteLine GetDateTime() & "-Bad " & "(" & tTIME & " sec)"
fsoDN.Close
Set fsoMail = fso.OpenTextFile(fileDN, 1)
strMsg = fsoMail.ReadAll
fsoMail.Close
vbSendMail Subject, MailTo, MailFrom, msg
End If
End If
Set fso = Nothing
End Sub
'__________________________________________________________________________________
Private Sub vbSendMail(SUBJECT, MAILTO, MAILFROM, MESSAGE)
Const SMTP = <SMTP Server>
Dim objCDOSYSCon, objCDOSYSMail
Set objCDOSYSMail = CreateObject("CDO.Message")
Set objCDOSYSCon = CreateObject ("CDO.Configuration")
objCDOSYSCon.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = SMTP
objCDOSYSCon.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
objCDOSYSCon.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
objCDOSYSCon.Fields("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout") = 10
objCDOSYSCon.Fields.Update
Set objCDOSYSMail.Configuration = objCDOSYSCon
'WScript.Echo "Type of objCDOSYSMail is " & TypeName(objCDOSYSMail)
objCDOSYSMail.From = MAILFROM
objCDOSYSMail.To = MAILTO
objCDOSYSMail.Subject = SUBJECT
objCDOSYSMail.TextBody = MESSAGE
objCDOSYSMail.Send
End Sub
'__________________________________________________________________________________
Function Ping(strHostname)
Ping = False
strQuery = "SELECT * FROM Win32_PingStatus WHERE Address = '" & strHostname & "'"
Set colPingResults = GetObject("winmgmts://./root/cimv2").ExecQuery(strQuery)
For Each objPingResult In colPingResults
If Not IsObject(objPingResult) Then
Ping = False
ElseIf objPingResult.StatusCode = 0 Then
Ping = True
Else
Ping = False
End If
Next
Set colPingResults = Nothing
End Function
'__________________________________________________________________________________
Function GetDateTime()
strDate = FormatDateTime(Date, 2)
strTime = FormatDateTime(Time, 3)
strDateY = Mid(strDate, InStrRev(strDate, "/") +1)
strDateD = Mid(strDate, InStr(strDate, "/") +1, InStrRev(strDate, "/") - InStr(strDate, "/") -1)
strDateM = Mid(strDate, 1, InStr(strDate, "/") -1)
If Len(strDateD) < 2 Then strDateD = "0" & strDateD
If Len(strDateM) < 2 Then strDateM = "0" & strDateM
strTimeH = Mid(strTime, 1, InStr(strTime, ":") -1)
strTimeN = Mid(strTime, InStr(strTime, ":") +1, InStrRev(strTime, ":") - InStr(strTime, ":") -1)
strTimeS = Mid(strTime, InStrRev(strTime, ":") +1, InStrRev(strTime, " ") - InStrRev(strTime, ":"))
If Len(strTimeH) < 2 Then strTimeH = "0" & strTimeH
GetDateTime = Trim(strDateY & "." & strDateM & strDateD & "." & strTimeH & strTimeN & strTimeS)
End Function
<message edited by mwalkowi on Wednesday, March 31, 2010 1:54 PM>