Can I add a Timeout to a Function, VBscript

Answered Can I add a Timeout to a Function, VBscript

  • Wednesday, August 08, 2012 2:16 PM
     
      Has Code

    Hey guys. I have a script that connects to remote PC's and checks for running processes via WMI. The problem is, every once in a while there is a system out there that seems to hang my script at the WMI check. There is no error produced that I can seem to catch. I am wondering if there is a way to make the "connect to WMI" function" in my script timeout after 30 seconds and then continue with the rest of the script? Here is my code:

    Function ConnectWMI(CompName)
    	ProcessList=Null
    	OSNameList=Null
    	OSArchList=Null
    	oLogFile.Write Now()&" - "&  "Connecting to WMI on "&CompName&"..."
    	Set oWMIService=GetObject("winmgmts:"_
    		& "{impersonationLevel=impersonate}!\\" & CompName & "\root\cimv2")
    	If Err.Number <> 0 Then
    		oLogFile.WriteLine "WMI Connection error #"&Err.Number&" on "&CompName&": "&Err.Description
    		WMIconn=False
    		ErrorExists=True
    		oLogFile.WriteLine Now()&" - "&"ERROR CONDITION EXISTS...WMI FAILED"
    		ConnectWMI=False
    		Err.Clear
    		Else
    			oLogFile.WriteLine "WMI connection to "&CompName&" was successfull. Querying WMI for running processes."
    			Set ProcessList=GetObject("winmgmts://"&CompName).InstancesOf("win32_process")
    			WMIconn=True
        		ConnectWMI=True
    	End If
    End Function

    It seems to get stuck at the Set ProcessList=GetObject("winmgmts://"&CompName).InstancesOf("win32_process") statement every once in a while on the odd computer.

    Any ideas? thanks in advance.

All Replies

  • Wednesday, August 08, 2012 2:26 PM
     
     

    That means it i s time to fix your WMI on those computers.  The call will timeout in 60 secods for any computer where WMI is working correctly or for any computer that does not respond.


    ¯\_(ツ)_/¯

  • Wednesday, August 08, 2012 2:31 PM
     
     

    Yes, I am aware that WMI needs to be fixed on the computer that causes my script to hang. But I need the script to move on from the problem computer and continue checking the other computers in the list.

    I'm not sure what you mean by "the call will timeout in 60 seconds"...obviously that is not happening with my script, hence my original question.

  • Wednesday, August 08, 2012 3:45 PM
     
     Answered

    Sorry but that is a well know deadlock.  There is no way to timeout a deadlock.

    In many cases it is just extremly slow response for a very large query.

    If you succeed on teh conenct then the query can lock.  The connect will always timeout.

    YOU can try adding a query timeout but that has not been successful when the remote or local WMI are corrupt.

    With PowerShell this is easy:

    $wmi = [wmisearcher]''
    #Set timeout for 10 seconds
    $wmi.options.timeout = "0:0:10"

    In VBS we can do this:

    server = "localhost"
    namespace = "root\cimv2"
    UserName = ""
    Password = ""
    'iSecFlag 0 '= forever
    iSecFlag = 128 '= (2 minutes) wbemConnectFlagUseMaxWait

    set loc = CreateObject("WbemScripting.SWbemLocator" )
    Set WBemServices = loc.ConnectServer(Server, Namespace,UserName,password,,,iSecFlag)

    You can also change your query from a Get to an ExecQuery:

    Set ProcessList = WBemServices.ExecQuery("select * from win32_process",,48)

    Note the 48 in the flags field.


    ¯\_(ツ)_/¯

  • Thursday, August 30, 2012 7:50 PM
     
     
    Thanks jrv! sorry it took me so long to get back to you. I will try out the code you suggested...but I'm wondering: Is there a way to 'catch' the 2 minute timeout event if it happens?
  • Thursday, August 30, 2012 8:25 PM
     
     
    Thanks jrv! sorry it took me so long to get back to you. I will try out the code you suggested...but I'm wondering: Is there a way to 'catch' the 2 minute timeout event if it happens?

    It throws an exception.  Use try/catch.


    ¯\_(ツ)_/¯

  • Thursday, August 30, 2012 9:31 PM
     
     

    So I have tried out your code as suggested, unfortunately the script still hangs indefinitely at the line:

    Set ProcessList= WbemServices.ExecQuery("select * from win32_process",,16)

    It seems I am able to connect to the WMI namespace on the PC no problem, but as soon as I try to query WMI it hangs the script.

    Is there any other way you can think of to catch a hung WMI query and allow the script to continue? Maybe I have to learn some PowerShell.

    thanks again for your help.

  • Thursday, August 30, 2012 9:42 PM
     
     

    So I have tried out your code as suggested, unfortunately the script still hangs indefinitely at the line:

    Set ProcessList= WbemServices.ExecQuery("select * from win32_process",,16)

    It seems I am able to connect to the WMI namespace on the PC no problem, but as soon as I try to query WMI it hangs the script.

    Is there any other way you can think of to catch a hung WMI query and allow the script to continue? Maybe I have to learn some PowerShell.

    thanks again for your help.

    Which is whty I posted this:

    Sorry but that is a well know deadlock.  There is no way to timeout a deadlock. In many cases it is just extremly slow response for a very large query. If you succeed on the conenct then the query can lock.  The connect will always timeout. You can try adding a query timeout but that has not been successful when the remote or local WMI are corrupt.

    Please read that carefully.  You have a corrupt WMI system that needs to be fixed.  That is not a scripting issue.

    I have never seen a query issue with Win32_process except when WMI was corrupt or teh system was corrupt.

    Fix your problem and the issue will go away.

    Running sepearate queries as separate jobs wil allow you to monitor the job and time it out.  This is not WMI that is doing this but is you and your script.  WMI will still hang.

    So process the call to the function as a job with start-job or other call


    ¯\_(ツ)_/¯

  • Thursday, August 30, 2012 10:01 PM
     
     

    Sorry jrv, I thought the code you provided was meant as a possible solution to the 'well known deadlock'.

    I understand I need to fix WMI on the system the script is hanging on. I can fix WMI on the system in question, but eventually another system in our company will have a similar issue in the future. Also, fixing WMI would be handled by our desktop support team, but they won't know they have anything to fix unless I can catch the problem and alert them about it. But this script is ran as a non-interactive scheduled task from a server. So if the script just hangs, there is no way to know something is wrong unless I manually go check on the status of the script, thus defeating the purpose of my automated task!

    Anyways...so, your suggestion of "process the call to the function as a job with start-job or other call" could potentially work around the 'known deadlock' issue? If so, could you elaborate?

    thanks again for your help and patience!

  • Thursday, August 30, 2012 10:11 PM
     
     

    Nothing to el;aborate omn.  Just run the function as a jopb/  Sen the copmputername as the argument and use the computer name to name the job.  You then just need to monitor the jobs.  If one hangs just kill it and report teh failure.


    ¯\_(ツ)_/¯

  • Thursday, August 30, 2012 10:39 PM
     
      Has Code

    Thats exactly what I need some more info on..."just run the function as a job". I am not sure how to do this in vbscript? I have been googling and "run function as job" doesn't not return much help.

    I am already calling my ConnectWMI(ComputerName) function from another sub/function in the script and passing the computer name. Are you saying I need to put my WMIQuery in another .vbs file and call it from the first vbs? Like:

    QueryResult = Wscript.Shell.Run "cscript.exe QueryWMI.vbs",True
    Wscript.sleep 6000
    If QueryResult<>0 Then
     'QueryWMI.vbs has not finished yet
     'Kill the QueryWMI.vbs process, somehow(!?)
    Else
     'QueryWMI.vbs finished in 60 seconds
    End If
    Thanks again for your help and sorry for being such a noob!

  • Thursday, August 30, 2012 10:49 PM
     
     

    Use PowerSHell.  YOU can't do it with VBScript.


    ¯\_(ツ)_/¯

  • Thursday, August 30, 2012 11:35 PM
     
      Has Code

    Thats exactly what I need some more info on..."just run the function as a job". I am not sure how to do this in vbscript? I have been googling and "run function as job" doesn't not return much help.

    I am already calling my ConnectWMI(ComputerName) function from another sub/function in the script and passing the computer name. Are you saying I need to put my WMIQuery in another .vbs file and call it from the first vbs? Like:

    QueryResult = Wscript.Shell.Run "cscript.exe QueryWMI.vbs",True
    Wscript.sleep 6000
    If QueryResult<>0 Then
     'QueryWMI.vbs has not finished yet
     'Kill the QueryWMI.vbs process, somehow(!?)
    Else
     'QueryWMI.vbs finished in 60 seconds
    End If
    Thanks again for your help and sorry for being such a noob!

    You can do it in VB Script by creating an auxiliary WMI process and monitoring it for x seconds. If it terminates successfully, do the real thing. If it hangs, get the main process to kill it, then move on to the next machine. 

    Set oWshShell = CreateObject("WScript.Shell")
    Set oFSO = CreateObject("Scripting.FileSystemObject")
    sSemaphore = oWshShell.ExpandEnvironmentStrings("%temp%\Semaphore.txt")
    iTimeout = 10 'seconds

    'If the parameter count is 0, process all PCs
    'If it is > 0, check if the PC can be accessed with WMI
    If WScript.Arguments.count = 0 Then
      Set oFile = oFSO.OpenTextFile("d:\PCs.txt", 1, False)
      aPCs = Split(oFile.ReadAll, VbCrLf)
      oFile.Close
      For Each sPC In aPCs
        Process(sPC)
      Next
    Else
      TestWMI(WScript.Arguments(0))
      WScript.Quit
    End If

    'Process one PC
    '1. Create a semaphore file.
    '2. Invoke this script again with two parameters: The PC name plus a bar (|).
    '   Use the Run method but do not wait for it to end.
    '3. Check during the timeout period if the semaphore file still exists.
    '   If it does not, continue with the WMI code.
    '4. If the timeout expires and the file still exists, kill the WMI test
    '   process, then exit to continue with the next PC.
    Function Process(CompName)
        WScript.Echo "Checking", CompName
        Set oFile = oFSO.CreateTextFile(sSemaphore, True)
       oFile.close
        oWshShell.Run "wscript.exe """ & WScript.ScriptFullName & """ " & CompName & " |", 0, False
       
       i = 0
       s = Second(Time())
       Do
          if not ofso.FileExists(sSemaphore) then Exit Do
          If s <> Second(Time()) Then 
            i = i + 1
            if i > iTimeout then Exit Do
            s = Second(Time())
          End If
       Loop

       If oFSO.FileExists(sSemaphore) Then
          Set oWMIService = GetObject("winmgmts:\\.\root\cimv2")
          Set colItems = oWMIService.ExecQuery("Select * From Win32_Process")
          For Each oItem In colItems
            If InStr(oItem.CommandLine, "|") > 0 Then 
              WScript.Echo "Killing process", oItem.ProcessId
              oItem.Terminate
              Exit Function
            End If
          Next
       Else
          ConnectWMI(CompName)
       End If
    End Function

    Function ConnectWMI(CompName)
       WScript.Echo "Processing", CompName
       Set oLogFile = oFSO.CreateTextFile("d:\Log.txt", True)
       ProcessList=Null
       OSNameList=Null
       OSArchList=Null
       oLogFile.Write Now()&" - "&  "Connecting to WMI on "&CompName&"..."
       Set oWMIService=GetObject("winmgmts:"_
          & "{impersonationLevel=impersonate}!\\" & CompName & "\root\cimv2")
       If Err.Number <> 0 Then
          oLogFile.WriteLine "WMI Connection error #"&Err.Number&" on "&CompName&": "&Err.Description
          WMIconn=False
          ErrorExists=True
          oLogFile.WriteLine Now()&" - "&"ERROR CONDITION EXISTS...WMI FAILED"
          ConnectWMI=False
          Err.Clear
       Else
          oLogFile.WriteLine "WMI connection to "&CompName&" was successfull. Querying WMI for running processes."
          Set ProcessList=GetObject("winmgmts://"&CompName).InstancesOf("win32_process")
          WMIconn=True
          ConnectWMI=True
       End If
    End Function

    Function TestWMI(CompName)
       Set oWMIService=GetObject("winmgmts:"_
          & "{impersonationLevel=impersonate}!\\" & CompName & "\root\cimv2")
       Set ProcessList = GetObject("winmgmts://" & CompName).InstancesOf("win32_process")
       oFSO.DeleteFile(sSemaphore)
    End Function

  • Friday, August 31, 2012 12:00 AM
     
     

    Powershell does it much more easily but go ahead.


    ¯\_(ツ)_/¯

  • Friday, August 31, 2012 5:49 AM
     
     

    Powershell does it much more easily but go ahead.


    ¯\_(ツ)_/¯

    Agreed. On the other hand it CAN be done with VB Script, contrary to what you thought initially.
  • Friday, August 31, 2012 9:49 AM
     
     

    Powershell does it much more easily but go ahead.


    ¯\_(ツ)_/¯

    Agreed. On the other hand it CAN be done with VB Script, contrary to what you thought initially.

    It makes more sense to fix WMI.

    I used to have that issue on more than onenetork,  Since fixing WMI and properly configuring the networks I have never seen that problem again.


    ¯\_(ツ)_/¯