Hack 5 Wait for and Optionally Terminate a Process

figs/expert.gif figs/hack05.gif

If you've wondered how to write code that waits for a process to finish before terminating it, here's the answer.

I have seen a number of discussions regarding the need for a VB script that waits for a process to finish. The script in this hack does this and more: it waits for a process to finish and optionally terminates the process if it has not finished within a specified amount of time.

This code is a modified form of what I use to control my software deployments, and it has two purposes. First, the code is designed to be certain that the deployment script waits until the initiated software setup executable is fully finished before proceeding. Even though the majority of recent software releases do not require this functionality when being deployed, it is still required for some legacy installations. Second, the code can perform a forceful termination of an application if this functionality is required.

This script accepts three arguments: the name of the executable to wait for or terminate, the amount of time to wait before terminating the specified executable, and (optionally) a switch specifying that the script should run silently. Note that the script uses Windows Management Instrumentation (WMI) for the process-management tasks, so make sure you're running the latest WMI version on your machine.

The Code

The script consists of several sections, which are described inline in the following sections.

Main routine

First, command-line switches are read in the main body area:

Option Explicit

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' File:     vbsWaitForProcess.vbs

' Updated:  Nov 2002

' Version:  1.0

' Author:   Dan Thomson, myITforum.com columnist

'           I can be contacted at dethomson@hotmail.com

'

' Usage:    The command processor version must be run using cscript

'           cscript vbsWaitForProcess.vbs notepad.exe 60 S

'           or

'           The IE and Popup versions can be run with cscript or wscript

'           wscript vbsWaitForProcess.vbs notepad.exe -1

'

' Input:    Name of executable  (ex: notepad.exe)

'           Time to wait in seconds before terminating the executable

'               -1 waits indefinitely for the process to finish

'               0 terminates the process imediately

'               Any value > 0 will cause the script to wait the specified

'               amount of time in seconds before terminating the process

'           Silent mode  (S)

'

' Notes:

'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''



On Error Resume Next



'Define some variables

Dim strProcess

Dim intWaitTime

Dim strSilent



'Get the command line arguments

strProcess = Wscript.Arguments.Item(0)

intWaitTime = CInt(Wscript.Arguments.Item(1))

strSilent = Wscript.Arguments.Item(2)



Call WaitForProcess (strProcess, intWaitTime, strSilent)
Check if process is running

Next, the ProcessIsRunning function determines if a process is running:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' Function: ProcessIsRunning

'

' Purpose:  Determine if a process is running

'

' Input:    Name of process

'

' Output:   True or False depending on if the process is running

'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Private Function ProcessIsRunning( strProcess )

    Dim colProcessList



    Set colProcessList = Getobject("Winmgmts:").Execquery _

        ("Select * from Win32_Process Where Name ='" & strProcess & "'")

    If colProcessList.Count > 0 Then

        ProcessIsRunning = True

    Else

        ProcessIsRunning = False

    End If



    Set colProcessList = Nothing

End Function
Terminate the process

In the next section, the ProcessTerminate function terminates a process:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' Function: TerminateProcess

'

' Purpose:  Terminates a process

'

' Input:      Name of process

'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Private Function ProcessTerminate( strProcess )

    Dim colProcessList, objProcess



    Set colProcessList = GetObject("Winmgmts:").ExecQuery _

        ("Select * from Win32_Process Where Name ='" & strProcess & "'")

    For Each objProcess in colProcessList

        objProcess.Terminate( )

    Next



    Set colProcessList = Nothing

End Function
Wait for process to terminate

Finally, in the WaitForProcess subroutine, the user interface is set up, the script waits while the process is active, and the process termination is initiated. I created three versions of the subroutine in an effort to demonstrate a few methods for displaying status messages. For example, here's how to display these messages using the command console:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' Sub: WaitForProcess

'

' Purpose:  Waits for a process

'

' Input:    Name of process

'           Wait time in seconds before termination.

'             -1 will cause the script to wait indefinitely

'             0 terminates the process imediately

'             Any value > 0 will cause the script to wait the specified

'             amount of time in seconds before terminating the process

'           Display mode.

'             Passing S will run the script silent and not show any prompts

'

' Output:   On screen status

'

' Notes:    The version echos user messages in the command window via StdOut

'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Private Sub WaitForProcess( strProcess, intWaitTime, strMode )



  If ProcessIsRunning(strProcess) Then

    Dim StdOut

    Dim w : w = 0

    Dim strPrompt

    Dim intPause : intPause = 1



    If UCase(strMode) <> "S" Then

      strPrompt = "Waiting for " & strProcess & " to finish."

      Set StdOut = WScript.StdOut

      StdOut.WriteLine ""

      StdOut.Write strPrompt

    End If

    'Loop while the process is running

    Do While ProcessIsRunning(strProcess)

      'Check to see if specified # of seconds have passed before terminating

      'the process. If yes, then terminate the process

      If w >= intWaitTime AND intWaitTime >= 0 Then

        Call ProcessTerminate(strProcess)

        Exit Do

      End If

      'If not running silent, post user messages

      If UCase(strMode) <> "S" Then _

        StdOut.Write "."

      'Increment the seconds counter

      w = w + intPause

      'Pause

      Wscript.Sleep(intPause * 1000)

    Loop

    If UCase(strMode) <> "S" Then

      StdOut.WriteLine ""

      Set StdOut = Nothing

    End If

  End If

End Sub

The result is shown in Figure 1-8.

Figure 1-8. Status message displayed in command console
figs/wsh_0108.gif

Alternatively, here's some code for displaying status messages in Internet Explorer:

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' Sub: WaitForProcess

'

' Purpose:  Waits for a process

'

' Input:    Name of process

'           Wait time in seconds before termination.

'             -1 will cause the script to wait indefinitely

'             0 terminates the process imediately

'             Any value > 0 will cause the script to wait the specified

'             amount of time in seconds before terminating the process

'             Display mode.

'             Passing S will run the script silent and not show any prompts

'

' Output:   On screen status

'

' Notes:    This version uses Internet Explorer for user messages

'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Private Sub WaitForProcess( strProcess, intWaitTime, strMode )



  If ProcessIsRunning(strProcess) Then

    Dim objIntExplorer

    Dim c : c = 0

    Dim w : w = 0

    Dim strPrompt

    Dim intPause : intPause = 1



    strPrompt = "Waiting for " & strProcess & " to finish."



    'If not running silent, create reference to objIntExplorer

    'This will be used for the user messages. Also set IE display attributes

    If UCase(strMode) <> "S" Then

      Set objIntExplorer = Wscript._

      CreateObject("InternetExplorer.Application")

      With objIntExplorer

        .Navigate "about:blank"

        .ToolBar = 0

        .Menubar = 0         ' no menu

        .StatusBar = 0

        .Width=400

        .Height = 80

        .Left = 100

        .Top = 100

        .Document.Title = "WaitForProcess"

      End With

      'Wait for IE to finish

      Do While (objIntExplorer.Busy)

          Wscript.Sleep 200

      Loop

      'Show IE

      objIntExplorer.Visible = 1

    End If

    Do While ProcessIsRunning(strProcess)

      'Check to see if specified # of seconds have passed before terminating

      'the process. If yes, then terminate the process

      If w >= intWaitTime AND intWaitTime >= 0 Then

        Call ProcessTerminate(strProcess)

        Exit Do

      End If

      If UCase(strMode) <> "S" Then

        objIntExplorer.Document.Body.InnerHTML = strPrompt & String(c, ".")

        'Increment the counter.

        'Reset the counter indicator if it's > 25 because

        'we don't want it taking up a lot of screen space.

        If c > 25 Then c = 1 Else c = c + 1

        'Increment the seconds counter

        w = w + intPause

      End If

      'Pause

      Wscript.Sleep(intPause * 1000)

    Loop

    objIntExplorer.Quit( )             ' close Internet Explorer

    Set objIntExplorer = Nothing      ' release object reference



  End If

End Sub

The resulting status message is shown in Figure 1-9.

Figure 1-9. Displaying status messages in Internet Explorer
figs/wsh_0109.gif

Finally, here's code that uses the Popup method of Windows Scripting Host for displaying status messages:

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' Sub: WaitForProcess

'

' Purpose:  Waits for a process

'

' Input:    Name of process

'           Wait time in seconds before termination.

'             -1 will cause the script to wait indefinitely

'             0 terminates the process imediately

'             Any value > 0 will cause the script to wait the specified ' 

'             amount of time in seconds before terminating the process

'           Display mode.

'             Passing S will run the script silent and not show any prompts

'

' Output:   On screen status

'

' Notes:    This version uses WshShell.Popup for user messages

'

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Private Sub WaitForProcess( strProcess, intWaitTime, strMode )



  If ProcessIsRunning(strProcess) Then

    Dim objWshShell

    Dim c : c = 0

    Dim w : w = 0

    Dim strPrompt

    Dim intPopupTimer : intPopupTimer = 2

    Dim intPause : intPause = 1



    strPrompt = "Waiting for " & strProcess & " to finish."



    'If not running silent, create reference to objWshShell

    'This will be used for the user messages

    If UCase(strMode) <> "S" Then _

      Set objWshShell = CreateObject("WScript.Shell")

    'Loop while the process is running

    Do While ProcessIsRunning(strProcess)

      'Check to see if specified # of seconds have passed before terminating

      'the process. If yes, then terminate the process

      If w >= intWaitTime AND intWaitTime >= 0 Then

        Call ProcessTerminate(strProcess)

        Exit Do

      End If

      'If not running silent, post user prompt

      If UCase(strMode) <> "S" Then

        objWshShell.Popup strPrompt & String(c, "."), intPopupTimer, _

        "WaitForProcess", 64

        'Increment the counter.

        'Reset the counter indicator if it's > 25 because

        'we don't want it taking up a lot of screen space.

        If c > 25 Then c = 1 Else c = c + 1

      End If

      'Increment the seconds counter

      w = w + intPause + intPopupTimer

      'Pause

      Wscript.Sleep(intPause * 1000)

    Loop

    Set objWshShell = Nothing

  End If

End Sub

The resulting dialog box is shown in Figure 1-10.

Figure 1-10. Displaying status messages in a dialog box
figs/wsh_0110.gif

Note that if you are assembling a standalone script, it should contain sections 1, 2, 3, and one option from section 4. If you would rather incorporate this code into your existing script, you need only sections 2, 3, and one option from section 4. You'll also need to add the call statement that is at the end of the main routine section. All the code sections are self-contained, which makes them easy to import into existing scripts.

Running the Hack

To use this hack, type the code into Notepad (with Word Wrap disabled) and save it with a .vbs extension as WaitForProcess.vbs. Or, if you don't want to tire your fingers out, download it from the O'Reilly web site instead.

Here are a few sample command-line examples. This will wait indefinitely until Notepad is closed:

cscript WaitForProcess.vbs notepad.exe -1

This will wait silently and indefinitely until Notepad is closed:

cscript WaitForProcess.vbs notepad.exe -1 S

And this will wait 10 seconds before Notepad is forcefully closed:

cscript WaitForProcess.vbs notepad.exe 10

?Dan Thomson