Search code examples
timevbscriptprocesswmikill

Intermittently can't kill a process if it runs too long


Here or there I have found Cleanmgr.exe and Ccleaner to hang. When they do, typically the CPU usage is upwards of 90%+. The hangs are intermittent and hard to reproduce, but when they hang, task manager has reported running them running for over 8 hours. 99% CPU usage is typical for a few seconds.

So I wrote a short little vbScript to run the apps, then kill it if it takes too long - I'm thinking no more than 3 minutes per app. FYI, I'm running out of box, from WinXp to 8.1, so I really only have vbScript and the command line.

First attempt appeared successful, but then I found I had to apply a second test, again with another timer, however, now I find my script doesn't exit at all when Cleanmgr or CCleaner hangs.

This started simple, and now it's nuts. I'm hoping someone out here can help me. I think the issue is, the process is chewing up my CPU, so the timer check in my script can't run...

It occurred to me, I'm calling this from a cmd file using cScript - could there be some issue there?

Is there a way to track the process time rather than from a timer? Create a thread higher priority than the process so it can terminate when it hangs? Maybe I have bug in my code? Help please, I'm going nuts. Thank you.

Option Explicit
On Error Goto 0

Dim wshShell, sysPath, waitTime, i, str, masterTimer, mElapsed, slp
Dim apps(), paths(), params()

Set wshShell=WScript.CreateObject("wScript.Shell")
sysPath  = wshShell.ExpandEnvironmentStrings("%SystemRoot%")
waitTime = 90
slp      = 2255

ReDim apps(2)
ReDim paths(2)
ReDim params(2)

apps(0)   = "cleanmgr.exe"
paths(0)  = sysPath&"\System32"
params(0) = "/SageRun:101"
apps(1)   = "ccleaner.exe"
paths(1)  = "C:\Program Files\ccleaner"
params(1) = "/AUTO"
apps(2)   = "ccleaner64.exe"
paths(2)  = "C:\Program Files\ccleaner"
params(2) = "/AUTO"

For i=LBound(apps) to UBound(apps)
 str="cmd.exe /C taskkill.exe /im " & apps( i ) & " /f /t"
 wshShell.run str
 str="cmd.exe /C taskkill.exe /im " & apps( i ) & " /f"
 wshShell.run str
 str="cmd.exe /C tskill.exe "       & apps( i ) & " /a /v"
 wshShell.run str
 WScript.Sleep slp
  masterTimer = Timer
  mElapsed    = 0
  RunCleaner paths(i),apps(i),params(i)
 str="cmd.exe /C taskkill.exe /im " & apps( i ) & " /f /t"
 wshShell.run str
 str="cmd.exe /C taskkill.exe /im " & apps( i ) & " /f"
 wshShell.run str
 str="cmd.exe /C tskill.exe "       & apps( i ) & " /a /v"
 wshShell.run str
 Set Str=Nothing
 Wscript.sleep slp
Next

ReDim apps(0)
ReDim paths(0)
ReDim params(0)
Erase apps
Erase paths
Erase params

Set slp=Nothing
Set sysPath=Nothing
Set wshShell=Nothing
Set waitTime=Nothing

WScript.Quit(0)

Public Sub RunCleaner( strPath, prog, args )
 Dim objFSO, objWMIService, objProcess, objStartup, objConfig, colMonitoredProcesses, objLatestProcess
 Dim intProcessID, erReturn, processes, proc
 Dim fullPath, elapsed, startTime, running

 Set objFSO=CreateObject( "Scripting.FileSystemObject" )
 fullPath = "" & strpath & "\" & prog

 If objFSO.FileExists( fullPath ) Then
  Set objWMIService= GetObject( "winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2" )
  Set objProcess   = GetObject( "winmgmts:root\cimv2:Win32_Process" )
  Set objStartup   = objWMIService.Get( "Win32_ProcessStartup" )
  Set objConfig    = objStartup.SpawnInstance_
  objConfig.ShowWindow = 1
  elapsed   = -1 * slp
  startTime = Timer
  Wscript.sleep slp
  erReturn  = objProcess.Create ( fullPath & " " & args, Null, objConfig, intProcessID )
  Set colMonitoredProcesses = objWMIService. _        
                               ExecNotificationQuery( "select * From __InstanceDeletionEvent " _
                               & " within 1 where TargetInstance isa 'Win32_Process'" )

  Do While ( ( elapsed < waitTime ) And ( ( mElapsed ) < waitTime ) )
   Set objLatestProcess = colMonitoredProcesses.NextEvent
    If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
     Exit Do
    End If
   elapsed  = Timer - startTime
   mElapsed = Timer - masterTimer
  Loop

  WScript.sleep slp
  running   = True

  Do While ( ( running ) And ( elapsed < waitTime ) And ( mElapsed < waitTime ) )
   SET processes = GetObject( "winmgmts:" )
   running = False
   elapsed = ( Timer - startTime ) / 2
   For Each proc in processes.InstancesOf( "Win32_Process" )
    If ( StrComp( LCase( proc.Name ), LCase( prog ), vbTextCompare ) = 0 ) Then
     running = True
     Exit For
    End If
   Next
   Set processes=Nothing
   mElapsed = ( Timer - masterTimer ) / 2
  Loop

  WScript.sleep slp
  fullPath = "cmd.exe /C taskkill.exe /im " & prog & " /f /t"
  wshShell.run fullPath
  fullPath = "cmd.exe /C taskkill.exe /im " & prog & " /f"
  wshShell.run fullPath
  fullPath = "cmd.exe /C tskill.exe "       & prog & " /a /v"
  wshShell.run fullPath

  Set objWMIService=Nothing
  Set objProcess=Nothing
  Set objStartup=Nothing
  Set objConfig=Nothing
  Set objProcess=Nothing
  Set erReturn=Nothing
  Set intProcessID=Nothing
  Set colMonitoredProcesses=Nothing
  Set elapsed=Nothing
  Set startTime=Nothing
  Set objLatestProcess=Nothing
  Set running=Nothing-1 * slp
  Set proc=Nothing
  Set fullPath=Nothing
 End If
 Set objFSO=Nothing
End Sub

Solution

  • Your script seems a bit complex for what it needs to do. Try this out.

    The exes are executed consecutively. If any are running for longer than 3 minutes then they're terminated via taskkill. If you want to do away with WMI queries altogether (which is probably where your script is having to fight for CPU) you could simply wait three minutes for each exe you start and then send a taskkill without checking whether it's still running. You could also do a tasklist rather than a WMI query.

    Dim fso, shl, wmi
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set shl = CreateObject("WScript.Shell")
    Set wmi = GetObject("winmgmts:\\.\root\cimv2")
    
    Dim app1, app2, app3, apps()
    ReDim apps(-1)
    app1 = fso.BuildPath(shl.ExpandEnvironmentStrings("%SYSTEMROOT%"), "System32\cleanmgr.exe")
    app2 = "C:\Program Files\ccleaner\ccleaner.exe"
    app3 = "C:\Program Files\ccleaner\ccleaner64.exe"
    If fso.FileExists(app1) Then AddApp app1, "/SageRun:101"
    If fso.FileExists(app2) Then AddApp app2, "/AUTO"
    If fso.FileExists(app3) Then AddApp app3, "/AUTO"
    If UBound(apps) < 0 Then WScript.Quit ' None of the programs exist
    
    Dim apptorun, starttime, running, process
    For Each apptorun In apps
        starttime = Now
        process = fso.GetFile(apptorun.exe).Name
        shl.Run apptorun.exe & " " &  apptorun.param, 0, False
        Do While 1
            WScript.Sleep 5000
            Set running = wmi.ExecQuery("select * from win32_process where name = """ & process & """")
            If running.Count = 0 Then Exit Do
            If DateDiff("n", starttime, Now) > 3 Then
                shl.Run "taskkill /f /im " & process, 0, True
                Exit Do
            End If
        Loop
    Next
    
    
    Sub AddApp(app, arg)
        ReDim Preserve apps(UBound(apps) + 1)
        Set apps(UBound(apps)) = New program
        apps(UBound(apps)).exe = app
        apps(UBound(apps)).param = arg
    End Sub
    
    Class program
        Dim exe, param
    End Class