Solved

Hook program closing

Posted on 2008-11-03
5
307 Views
Last Modified: 2013-12-04
Hey,

I'm currently writing a program which uses Shell() to run a batch command line job. I have no problem getting a hwnd to the cmd window, but what I need to be able to do is detect when the cmd process ends. Can anyone help?

I'm posting this in areas other than VB6 hoping someone in the c++ section knows how to do this and I can adapt their code.

Thanks.
0
Comment
Question by:Corrup7ioN
5 Comments
 
LVL 86

Expert Comment

by:jkr
ID: 22868160
Since 'Shell()' returns the process ID, you can use this one to wait for the task to complete, e.g. (C/C++)
HANDLE hProcess = OpenProcess(SYNCHRONIZE,FALSE,dwID);
 

WaitForSingleObject(hhProcess,INFINITE);
 

CloseHandle(hProcess);

Open in new window

0
 
LVL 86

Accepted Solution

by:
jkr earned 500 total points
ID: 22868251
BTW, see also http://www.thescarms.com/vbasic/wait.aspx ("Wait for a Process to Terminate w/ WaitForSingleObject"):


   Const SYNCHRONIZE = &H100000

   '

   ' Wait forever

   '

   Const INFINITE = &HFFFF

   '

   ' The state of the specified object is signaled

   '

   Const WAIT_OBJECT_0 = 0

   '

   ' The time-out interval elapsed & the objects state is not signaled

   '

   Const WAIT_TIMEOUT = &H102
 

   Private Declare Function OpenProcess Lib "kernel32" ( _

      ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, _

      ByVal dwProcessId As Long) As Long
 

   Private Declare Function WaitForSingleObject Lib "kernel32"

     (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
 

   Private Declare Function CloseHandle Lib "kernel32" ( _

      ByVal hObject As Long) As Long
 
 

-----------------------------------------
 

' The WaitForSingleObject function returns when one of the following occurs:

   ' - The specified object is in the signaled state.

   ' - The time-out interval elapses.

   '

   ' The dwMilliseconds parameter specifies the time-out interval, in milliseconds.

   ' The function returns if the interval elapses, even if the objects state is

   ' nonsignaled. If dwMilliseconds is zero, the function tests the objects state

   ' and returns immediately. If dwMilliseconds is INFINITE, the functions time-out 

   ' interval never elapses.

   '

   ' This example waits an INFINITE amount of time for the process to end. As a

   ' result this process will be frozen until the shelled process terminates. The

   ' down side is that if the shelled process hangs, so will this one.

   '

   ' A better approach is to wait a specific amount of time. Once the time-out

   ' interval expires, test the return value. If it is WAIT_TIMEOUT, the process

   ' is still not signaled. Then you can either wait again or continue with your

   ' processing.

   '

   ' DOS Applications:

   ' Waiting for a DOS application is tricky because the DOS window never goes

   ' away when the application is done. To get around this, prefix the app that

   ' you are shelling to with "command.com /c".

   '

   ' For example: lPid = Shell("command.com /c " & txtApp.Text, vbNormalFocus)

   '

   ' To get the return code from the DOS app, see the attached text file.

   '

   Dim lPid As Long

   Dim lHnd As Long

   Dim lRet As Long
 

   If Trim$(txtApp) = "" Then Exit Sub
 

   lPid = Shell(txtApp.Text, vbNormalFocus)
 

   If lPid <> 0 Then
 

      'Get a handle to the shelled process.

      lHnd = OpenProcess(SYNCHRONIZE, 0, lPid)

      

      'If successful, wait for the application to end and close the handle.

      If lHnd <> 0 Then

         lRet = WaitForSingleObject(lHnd, INFINITE) CloseHandle (lHnd)

      End If
 

      MsgBox "Just terminated.", vbInformation, "Shelled Application"

   End If

Open in new window

0
 
LVL 16

Expert Comment

by:HooKooDooKu
ID: 22868403
The code snippet below is the WaitShell function I wrote for myself.  The note in it make reference to the point jkr's notes make about a DOS window staying open after the BAT has executed.  In my case, I had found the command windows had a checkbox option somewhere that would cause the window to auto close when done.  Of course, the subroutine could be modified to use the option jkr's notes make mention about.

The concept is similar to what the above codes use, execpt that the WaitShell queries the return code from the Shell application (so you can determine the return code from the program if you ran an EXE in the WaitShell) because it loops checking for a return code rather than looking for when all other processes have terminated.
Public Const STILL_ACTIVE = &H103
 

Public Type PROCESS_INFORMATION

        hProcess As Long

        hThread As Long

        dwProcessId As Long

        dwThreadId As Long

End Type
 

Public Type STARTUPINFO

        cb As Long

        lpReserved As String

        lpDesktop As String

        lpTitle As String

        dwX As Long

        dwY As Long

        dwXSize As Long

        dwYSize As Long

        dwXCountChars As Long

        dwYCountChars As Long

        dwFillAttribute As Long

        dwFlags As Long

        wShowWindow As Integer

        cbReserved2 As Integer

        lpReserved2 As Long

        hStdInput As Long

        hStdOutput As Long

        hStdError As Long

End Type

Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long

Public Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As SECURITY_ATTRIBUTES, lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long

Public Declare Function CreateProcess_API Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As Any, lpThreadAttributes As Any, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long

Public Declare Function ResumeThread Lib "kernel32" (ByVal hThread As Long) As Long

Public Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, ByRef lpExitCode As Long) As Long
 

'Run a command line program and stay here until execution of command is complete

' Returns the Exit Code from the call Program

'NOTE: If a DOS or BAT Program is executed and the DOS window remains on the

'      screen after the DOS Program has executed, this Function will remain in

'      its loop until the DOS Window is closed

'      (check the 'Close on Exit' Propert for the DOS Program)

Public Function WaitShell(CmdLine$, Optional WindowStyle As VbAppWinStyle = vbMinimizedFocus) As Long

Const Source As String = "WaitShell"

Dim CommandLine As String       'CmdLine is Placed in a different String so that it is not modified by the CreateProccess Function

Dim SUI As STARTUPINFO

Dim pi As PROCESS_INFORMATION

Dim hProcess As Long

Dim ExitCode As Long
 

    On Error GoTo CatchError

    

    'Initialize Required Variables

    CommandLine = CmdLine$          'Copy it because CreateProcess Function may try to change it (add a Null to the end)

    With SUI

        .cb = Len(SUI)

        .lpReserved = vbNullString

        .lpDesktop = vbNullString

        .lpTitle = vbNullString

        .cbReserved2 = 0

        .lpReserved2 = 0

        .dwFlags = 1

        .wShowWindow = WindowStyle

    End With

    

    'Create the Thread in a Suspended State

    If CreateProcess_API(vbNullString, CommandLine, ByVal 0, ByVal 0, 0, CREATE_SUSPENDED, ByVal 0, vbNullString, SUI, pi) = 0 Then

        'Handle error located in ERR.LastDLLError

    End If

    

    'Remember the Process Handle

    hProcess = pi.hProcess

    If hProcess = 0 Then

        'Handle the error in Err.LastDLLError Source

    End If

    

    'Release the Thread for Execution

    If ResumeThread(pi.hThread) = -1 Then

        'Handle the Err.LastDLLError Here

    End If

    

    'Watch for the Exit Code to indicate Process has finished

    Do

        If GetExitCodeProcess(hProcess, ExitCode) = 0 Then

            Debug.Assert Err.LastDllError <> 0

            'Handle Error in Err.LastDllError Source

        End If

        

        DoEvents

        Sleep 100

    Loop While ExitCode = STILL_ACTIVE

    

    WaitShell = ExitCode

        

    Call CloseHandle(pi.hProcess)

    Call CloseHandle(pi.hThread)

    

    Exit Function

    

CatchError:

    Call CloseHandle(pi.hProcess)

    Call CloseHandle(pi.hThread)

    'Error Handler Here - Such as return a value indicating an error or raise an error to calling function

End Function

Open in new window

0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 22869772
If you are displaying any type of gui such as a form for example you might want to consider pumping messages to your application so it can respond otherwise your form will not respond
Option Explicit
 

Private Const QS_ALLINPUT       As Long = (&H40 Or &H20 Or &H10 Or &H8 Or &H4 Or &H2 Or &H80 Or &H1)

Private Const WAIT_OBJECT_0     As Long = 0

Private Const SYNCHRONIZE       As Long = &H100000

Private Const INFINITE          As Long = &HFFFF
 

Private Declare Function MsgWaitForMultipleObjects Lib "user32.dll" ( _

    ByVal nCount As Long, _

    ByRef pHandles As Long, _

    ByVal fWaitAll As Long, _

    ByVal dwMilliseconds As Long, _

    ByVal dwWakeMask As Long) As Long

 

Private Declare Function OpenProcess Lib "kernel32.dll" ( _

    ByVal dwDesiredAccess As Long, _

    ByVal bInheritHandle As Long, _

    ByVal dwProcessId As Long) As Long

    

Private Declare Function CloseHandle Lib "kernel32.dll" ( _

    ByVal hObject As Long) As Long

    

Public Sub WaitGui(ByVal szPath As String)

    

    Dim hProcess            As Long

    Dim dwPid               As Long

    Dim dwResult            As Long

    

    dwPid = Shell(szPath, vbNormalFocus)

    

    If dwPid <> 0 Then

        hProcess = OpenProcess(SYNCHRONIZE, 0, dwPid)

        If hProcess <> 0 Then

            Do

                dwResult = MsgWaitForMultipleObjects(1, hProcess, 0, INFINITE, QS_ALLINPUT)

                DoEvents

            Loop Until dwResult = WAIT_OBJECT_0

        End If

    End If

    

    Call CloseHandle(hProcess)

 

End Sub

Open in new window

0
 
LVL 3

Author Comment

by:Corrup7ioN
ID: 22875305
Thanks for the help guys.

I used jkr's solution from thescarms, with a slight modification as suggested in the codes comments (see below). I opted for WaitForSingleObject as I don't have a gui, although I'm sure I will need MsgWaitForMultipleObjects in the future, so thank you.
Do

            

    lret = WaitForSingleObject(lHnd, 5000)

    

Loop While lret = WAIT_TIMEOUT

        

CloseHandle (lHnd)

        

If lret = wait_object Then

    'Terminated

Else

    'Some error

End If

Open in new window

0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

You can of course define an array to hold data that is of a particular type like an array of Strings to hold customer names or an array of Doubles to hold customer sales, but what do you do if you want to coordinate that data? This article describes…
Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now