I was driven to write this script for a client that was using Novell’s iPrint. iPrint was causing some threads to hang, ending the spoolsv.exe process, and therefore stopping the Print Spooler service. Those of you that are using such poor printing solutions would no doubt have clocked up a considerable number of headaches. The good news is that this script will provide you with some much deserved pain relief. Let’s call it the “Asprin for iPrint”. I’ll have to register a trade mark for that one đ
So why is this script so good? Glad you asked!
It monitors the spoolsv.exe process. If the process ends, it then checks to see if the service has cleanly stopped with an “Exit Code” of 0. If it hasn’t, it will clean out the spooler folder and then restart the Spooler service, including any dependants that are not running, such as the “Citrix Print Manager” service. The cool thing is that it time stamps the events, writes messages to the Event Logs, and sends e-mail alerts. Very cool for monitoring a problematic printing environment.
The script does a whole lot more, specifically around iPrint, and can really be adapted for any printing solution. The important thing is that it quickly and neatly restarts the Spooler service, preventing any user interruptions what so ever.
I’m currently running the script from the good old “autoexnt” Resouce Kit tool, so it runs at Startup as the System account. But you could also run it as a Startup Script from a Group Policy Object.
It is executed by the Autoexnt.bat using the following line…
start cscript //nologo "%SystemRoot%\System32\MonitorProcess.vbs" spoolsv.exe spooler
It’s worth mentioning here that I use the FREE w3 JMail COM Object from Dimac to send SMTP messages from within VBScripts. And as such, you will see how I create an instance of the jmail.message object set objJMail = CreateOBject(“JMail.Message”) and then specify the sender, recipient, subject, body and mail server. Therefore, this script will fail as is if you do not install JMail, or at least place a copy of the jmail.dll on the server and register it. Alternatively, you can comment out any calls to the SendMail subroutine.
MonitorProcess.vbs
' The purpose of this script is to monitor the spoolsv.exe process. If it ends, restart the Spooler service.
' I have tried to make this as generic as possible so that it can be used to monitor other processes and
' services.
' This was specifically written for a client who had implemented Novell's iPrint into their Citrix XenApp 4.5
' environment. iPrint was causing a lot of grief when adding printer objects by ending the spoolsv.exe process.
' Version 1.0
' Written by Jeremy@jhouseconsulting.com on 14th July 2008.
Option Explicit
Dim objArgs, strProcess, strFile, strService, j, k, blnFirstRun, strComputer
Dim objWMIService, colProcesses, blnDebugger, intDebugRuns
strComputer = "."
Set objArgs = WScript.Arguments
' Check to make sure we received arguments.
if objArgs.Count = 0 Then
 WScript.Echo "USAGE: cscript //nologo ''MonitorProcess.vbs'' ''<process>'' ''<service>''" & VbCr & VbCr & _
"Where 'process' is the process you wish to monitor, and 'service' is the" & VbCr & _
"short name of the service that needs to be restarted." & VbCr & VbCr & _
"For Example:" & VbCr & _
"cscript //nologo MonitorProcess.vbs spoolsv.exe spooler"
WScript.Quit(0)
End If
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
' Check to make sure the script is not already running, as only one copy of this script should be running at
' any time.
Set colProcesses = objWMIService.ExecQuery _
("SELECT commandline FROM Win32_Process WHERE commandline LIKE '%" & wscript.scriptname & "%'")
If colProcesses.Count > 1 Then
Set objArgs = Nothing
Set objWMIService = Nothing
Set colProcesses = Nothing
wscript.quit(0)
End If
blnFirstRun = True
' Setting this value to True will trigger the user mode debugger.
blnDebugger = True
' This value represents the number of times the debugger should run.
intDebugRuns = 5
Do Until j = 999
' Note how we never increment j. Therefore, it takes on the default value of 0, and never increments, meaning
' that this will loop forever.
strProcess = objArgs(0)
strFile = objArgs(0) & " monitoring.txt"
if objArgs.Count > 1 Then
strService = objArgs(1)
End If
If blnFirstRun Then
wscript.echo Now() & vbTab & "The script has initiated."
End If
Call MonitorProcess(strProcess,strService,strFile,blnFirstRun,blnDebugger)
blnFirstRun = False
If K = intDebugRuns-1 Then
blnDebugger = False
End If
k = k + 1
Loop
Set objArgs = Nothing
Set objWMIService = Nothing
Set colProcesses = Nothing
WScript.Quit(0)
Sub MonitorProcess(strProcess,strService,strFile,blnFirstRun,blnDebugger)
' This subroutine will monitor the process.
Dim objWMIService, colProcesses, objProcess, strTimeStamp, strMessage
Dim strComputer, strProcessPath, process, i, objFSO, intExitCode, strServiceMessage
Const HKEY_LOCAL_MACHINE = &H80000002
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set objFSO = CreateObject("Scripting.FileSystemObject")
' Check to see if the process is running.
Set colProcesses = objWMIService.ExecQuery _
("SELECT * FROM Win32_Process WHERE Name = '" & strProcess & "'")
If colProcesses.Count > 0 Then
If blnDebugger Then
Call RunDebugger(strProcess,blnFirstRun)
End If
' Get the path of the running process.
for each process in colProcesses
strProcessPath = Left(process.ExecutablePath, InstrRev(process.ExecutablePath, "\"))
' wscript.echo strProcessPath
Next
strFile = strProcessPath & strFile
If blnFirstRun Then
If objFSO.FileExists(strFile) Then
objFSO.DeleteFile(strFile), True
End If
End If
' Wait for the process to end.
Set colProcesses = objWMIService.ExecNotificationQuery _
("Select * From __InstanceDeletionEvent " _
& "Within 1 Where TargetInstance ISA 'Win32_Process'")
Do Until i = 999
' Note how we never increment i. Therefore, it takes on the default value of 0,
' and never increments, meaning that this will loop forever until the process ends.
Set objProcess = colProcesses.NextEvent
If lcase(objProcess.TargetInstance.Name) = lcase(strProcess) Then
Exit Do
End If
Loop
intExitCode=ServiceExitCode(strService)
If intExitCode <> 0 Then
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Process: " & strProcess & VbCr & "Status: Ended" & VbCr & "Timestamp: " & strTimeStamp
wscript.echo Now() & vbTab & "Process has ended."
Else
wscript.echo Now() & vbTab & "Process has ended because the service was cleanly stopped."
End If
Else
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Process: " & strProcess & VbCr & "Status: Not running" & VbCr & "Timestamp: " & strTimeStamp
wscript.echo Now() & vbTab & "Process not running."
Call WaitForProcessToStart(strProcess)
End If
If intExitCode <> 0 Then
Call WriteToEventLog(strMessage)
wscript.echo Now() & vbTab & "Message written to event log."
Call WriteToLogFile(strFile,strMessage)
wscript.echo Now() & vbTab & "Message written to log file."
Call SendMail("Process",strMessage)
wscript.echo Now() & vbTab & "E-mail alert sent."
If strService <> "" Then
Call RestartService(strService,strFile)
End If
End If
set objWMIService = Nothing
Set objFSO = Nothing
Set colProcesses = Nothing
End Sub
Function ServiceExitCode(strService)
' This function gets the exit code for the service and passes it back as a return value so that we know if the
' service has cleanly stopped.
Dim strComputer, objWMIService, colListOfServices, objService
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colListOfServices = objWMIService.ExecQuery _
("Select * from Win32_Service where Name='" & strService & "'")
For Each objService in colListOfServices
ServiceExitCode=objService.ExitCode
Next
Set objWMIService = Nothing
Set colListOfServices = Nothing
End Function
Sub WaitForProcessToStart(strProcess)
' This subroutine waits for the process to start
Dim strComputer, objWMIService, colMonitoredProcesses, l, objLatestProcess, strTimeStamp, strMessage
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colMonitoredProcesses = objWMIService. _
ExecNotificationQuery("select * from __instancecreationevent " _
& " within 1 where TargetInstance isa 'Win32_Process'")
Do Until l = 999
Set objLatestProcess = colMonitoredProcesses.NextEvent
If lcase(objLatestProcess.TargetInstance.Name) = lcase(strProcess) Then
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Process: " & strProcess & VbCr & "Status: Started" & VbCr & "Timestamp: " & strTimeStamp
wscript.echo Now() & vbTab & "Process started."
Exit Do
End If
Loop
Set objWMIService = Nothing
Set colMonitoredProcesses = Nothing
Set objLatestProcess = Nothing
End Sub
Sub RestartService(strService,strFile)
' This subroutine will restart the service if it has terminated abnormally, and will
' attempt to start any other stopped services that have this service set as a dependency.
Dim strComputer, objWMIService, objItem, objService, colListOfServices, return, strTimeStamp
Dim strMessage, objServiceList, objDependService
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colListOfServices = objWMIService.ExecQuery _
("Select * from Win32_Service Where Name ='" & strService & "'")
If colListOfServices.count > 0 Then
For Each objService in colListOfServices
If (lcase(objService.State)="stopped") AND (objService.ExitCode<>0) Then
' As an example, when the spoolsv.exe process ends, the exit code for the spooler
' service is 1067, which means that the process terminated unexpectedly.
wscript.echo Now() & vbTab & "The Service has stopped with an exit code of " & objService.ExitCode & "."
Else
return=objService.StopService()
If return=0 Then
wscript.echo Now() & vbTab & "Service successfully stopped."
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Service: " & objService.Name & VbCr & "Status: Stopped" & VbCr & "Timestamp: " & strTimeStamp
Call WriteToEventLog(strMessage)
wscript.echo Now() & vbTab & "Message written to event log."
Call WriteToLogFile(strFile,strMessage)
wscript.echo Now() & vbTab & "Message written to log file."
Call SendMail("Service",strMessage)
wscript.echo Now() & vbTab & "E-mail alert sent."
End If
End If
Select Case LCase(strService)
Case "spooler"
Call CleanOutSpoolerFolder()
Call CheckforiPrint()
End Select
return=objService.StartService()
If return=0 Then
wscript.echo Now() & vbTab & "The " & objService.Name & " service successfully started."
End If
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Service: " & objService.Name & VbCr & "Status: Started" & VbCr & "Timestamp: " & strTimeStamp
Call WriteToEventLog(strMessage)
wscript.echo Now() & vbTab & "Message written to event log."
Call WriteToLogFile(strFile,strMessage)
wscript.echo Now() & vbTab & "Message written to log file."
Call SendMail("Service",strMessage)
wscript.echo Now() & vbTab & "E-mail alert sent."
' Start dependent services
Set objServiceList = objWMIService.ExecQuery("Associators of " _
& "{Win32_Service.Name='" & strService & "'} Where " _
& "AssocClass=Win32_DependentService " & "Role=Antecedent" )
For Each objDependService In objServiceList
If objDependService.State = "Stopped" Then
return=objDependService.StartService()
If return=0 Then
wscript.echo Now() & vbTab & "The " & objDependService.Name & " service successfully started."
End If
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Service: " & objDependService.Name & VbCr & "Status: Started" & VbCr & "Timestamp: " & strTimeStamp
Call WriteToEventLog(strMessage)
wscript.echo Now() & vbTab & "Message written to event log."
Call WriteToLogFile(strFile,strMessage)
wscript.echo Now() & vbTab & "Message written to log file."
Call SendMail("Service",strMessage)
wscript.echo Now() & vbTab & "E-mail alert sent."
End If
Next
Next
Else
strTimeStamp="on " & Date() & " at " & Time()
strMessage="Service: " & strService & VbCr & "Status: Not found" & VbCr & "Timestamp: " & strTimeStamp
Call WriteToEventLog(strMessage)
wscript.echo Now() & vbTab & "Message written to event log."
Call WriteToLogFile(strFile,strMessage)
wscript.echo Now() & vbTab & "Message written to log file."
wscript.quit 0
End If
Set objWMIService = Nothing
Set colListOfServices = Nothing
Set objServiceList = Nothing
End Sub
Sub WriteToEventLog(strMessage)
' This subroutine will write an alert to the event logs.
Dim WshShell
Set WshShell = CreateObject("WScript.Shell")
WshShell.LogEvent 2, strMessage
Set WshShell = Nothing
End Sub
Sub WriteToLogFile(strFile,strMessage)
' This subroutine will write the entries to a text file
Dim objFSO, objFile
Const ForAppending = 8
Set objFSO = CreateObject("scripting.filesystemobject")
Set objFile = objFSO.OpenTextFile(strFile,ForAppending,True,0)
strMessage = Replace(strMessage,VbCr,vbCrLf) & vbCrLf
objFile.WriteLine strMessage
objFile.Close
Set objFile = Nothing
End Sub
Sub SendMail(strServiceorProcess,strMessage)
' This subroutine will send off an e-mail alert using the JMail COM Object from www.Dimac.net
Dim WshNetwork, sComputerName, strSMTPServer, strReceipent1, strSubject, strBody
Dim objJMail
Set WshNetwork = WScript.CreateObject("WScript.Network")
sComputerName = WshNetwork.ComputerName
strSMTPServer = "smtp.mydomain.com"
strReceipent1 = "CitrixAlerts@mydomain.com"
strSubject = "A " & strServiceorProcess & " has changed state on " & sComputerName & "."
strBody = Replace(strMessage,VbCr,vbCrLf)
set objJMail = CreateOBject("JMail.Message")
objJMail.From = "server.alerts@mydomain.com"
objJMail.FromName = "Server Alerts"
objJMail.AddRecipient strReceipent1
objJMail.Subject = strSubject
objJMail.Body = strBody
objJMail.Send(strSMTPServer)
Set WshNetwork = Nothing
set objJMail = Nothing
End Sub
Sub CleanOutSpoolerFolder()
' This subroutine will clean put the Spooler folder.
Dim oReg, strComputer, objFSO, strKeyPath, strValueName, strValue, objFolder, Subfolder
Const HKEY_LOCAL_MACHINE = &H80000002
StrComputer="."
Set oReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\default:StdRegProv")
Set objFSO = CreateObject("Scripting.FileSystemObject")
strKeyPath = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers"
strValueName = "DefaultSpoolDirectory"
oReg.GetStringValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,strValue
Wscript.Echo Now() & vbTab & "The spooler folder is: " & strValue
objFSO.DeleteFile(strValue & "\*.*"), True
Set objFolder = objFSO.GetFolder(strValue)
For Each Subfolder in objFolder.SubFolders
objFSO.DeleteFolder Subfolder.Path, True
Next
Wscript.Echo Now() & vbTab & "Deleted the contents of the spooler folder."
Set objFolder = Nothing
Set oReg = Nothing
Set objFSO = Nothing
End Sub
Sub CheckforiPrint()
' This subroutine checks for the existence of iPrint.
' If it's installed it checks to see if Trace is enabled.
' If Trace is enabled it checks for the Trace file.
' If the Trace file exists, it renames it before restarting the Spooler service.
' NOTE that iPrint should be doing this, but I found that it was unreliable. It
' was perhaps a timing issue!
Dim oReg, strComputer, objFSO, strKeyPath, strValueName, dwValue
Dim WshShell, sRegValue, sRegKey, strSystemDrive
Const HKEY_LOCAL_MACHINE = &H80000002
StrComputer="."
Set WshShell = CreateObject("WScript.Shell")
Set oReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\default:StdRegProv")
Set objFSO = CreateObject("Scripting.FileSystemObject")
strSystemDrive = WshShell.ExpandEnvironmentStrings("%SystemDrive%")
sRegKey = "HKLM\SOFTWARE\Novell-iPrint"
sRegValue = "HKLM\SOFTWARE\Novell-iPrint\Settings\TraceOn"
If RegKeyExists(sRegKey) Then
Wscript.Echo Now() & vbTab & "iPrint is installed."
If RegValueExists(sRegValue) Then
' Check to see if Trace is enabled
strKeyPath = "SOFTWARE\Novell-iPrint\Settings"
strValueName = "TraceOn"
oReg.GetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,dwValue
Select Case dwValue
Case 0
Wscript.Echo Now() & vbTab & "iPrint Trace is disabled."
Case 1
Wscript.Echo Now() & vbTab & "iPrint Trace is enabled."
' Check for existance Trace file.
If objFSO.FolderExists(strSystemDrive & "\NDPS") Then
If objFSO.FileExists(strSystemDrive & "\NDPS\ippTrace.txt") Then
Wscript.Echo Now() & vbTab & "An iPrint Trace File exists."
If objFSO.FileExists(strSystemDrive & "\NDPS\ippTrace.previous.txt") Then
objFSO.DeleteFile strSystemDrive & "\NDPS\ippTrace.previous.txt", 1
End If
objFSO.MoveFile strSystemDrive & "\NDPS\ippTrace.txt", strSystemDrive & "\NDPS\ippTrace.previous.txt"
Wscript.Echo Now() & vbTab & "Renamed iPrint Trace file to " & strSystemDrive & "\NDPS\ippTrace.previous.txt"
Else
Wscript.Echo Now() & vbTab & "The iPrint Trace file is missing."
End If
End If
Case Else
Wscript.Echo Now() & vbTab & "iPrint Trace setting is invalid."
End Select
End If
Else
Wscript.Echo Now() & vbTab & "iPrint is not installed."
End If
Set WshShell = Nothing
Set oReg = Nothing
Set objFSO = Nothing
End Sub
Function RegKeyExists(ByVal sRegKey)
' Returns True or False based on the existence of a registry key.
Dim sDescription, oShell
Set oShell = CreateObject("WScript.Shell")
RegKeyExists = True
sRegKey = Trim (sRegKey)
If Not Right(sRegKey, 1) = "\" Then
sRegKey = sRegKey & "\"
End If
On Error Resume Next
oShell.RegRead "HKEYNotAKey\"
sDescription = Replace(Err.Description, "HKEYNotAKey\", "")
Err.Clear
oShell.RegRead sRegKey
RegKeyExists = sDescription <> Replace(Err.Description, sRegKey, "")
On Error Goto 0
Set oShell = Nothing
End Function
Function RegValueExists(sRegValue)
' Returns True or False based of the existence of a registry value.
Dim oShell, RegReadReturn
Set oShell = CreateObject("WScript.Shell")
RegValueExists = True ' init value
On Error Resume Next
RegReadReturn = oShell.RegRead(sRegValue)
If Err.Number <> 0 Then
RegValueExists = False
End if
On Error Goto 0
Set oShell = Nothing
End Function
Sub RunDebugger(strProcess,blnFirstRun)
' ADPlus is a vbscript included with the Debugging Tools for Windows. To enable the
' monitoring of a process and Wait for a crash, use the following command:
' ADPlus will only provide a user mode dump. Refer to KB article 286350 for further
' information.
Dim WshShell, objFSO, strSystemDrive, strProgramFiles, strCommandLine
Set WshShell = CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
strSystemDrive = WshShell.ExpandEnvironmentStrings("%SystemDrive%")
strProgramFiles = WshShell.ExpandEnvironmentStrings("%ProgramFiles%")
If objFSO.FileExists(strProgramFiles & "\Debugging Tools for Windows\adplus.vbs") Then
If blnFirstRun Then
If objFSO.FolderExists(strSystemDrive & "\" & strProcess & "_dumps\") Then
objFSO.DeleteFolder strSystemDrive & "\" & strProcess & "_dumps", True
End If
End If
If NOT objFSO.FolderExists(strSystemDrive & "\" & strProcess & "_dumps\") Then
objFSO.CreateFolder(strSystemDrive & "\" & strProcess & "_dumps")
End If
strCommandLine = "cscript " & chr(34) & strProgramFiles & "\Debugging Tools for Windows\adplus.vbs" & chr(34) & " -quiet -crash -pn " & strProcess & " -o " & strSystemDrive & "\" & strProcess & "_dumps"
WshShell.Run strCommandLine, 1, FALSE
wscript.echo Now() & vbTab & "Running the debugger to monitor for a " & strProcess & " crash using the following command line..." & VbCrLf & strCommandLine
End If
Set WshShell = Nothing
Set objFSO = Nothing
End Sub
