Link to home
Start Free TrialLog in
Avatar of nyclio
nyclio

asked on

Shelled program needs to run from specified directory

I am writing a program to automate certain tasks for me.  Basically all it does is install a program, then copies a program patch (which is part of the software package) and that patch is supposed to run in the same directory that the program is installed to.

So here is what I am using (in plain english)

1. Run Installer
2. Copy patch.exe to InstalledDir
3. Shell ("InstalledDir/patch.exe")

However, the patch does not apply correctly because I get the error (from patch.exe) that the program path is not correct and it shows that it is using the same path I run MY program from and not the path that the file is actually in.  If I run the patch directly (without using VB) then it applies just fine (since VB copied it to the correct directory).

How can I correct this?  Any help is appreciated.  Thanks!
Avatar of Muhammad Khan
Muhammad Khan
Flag of Canada image

can you create a batch file which uses
CD installedDir
Patch.exe
and then Shell "MyBat.bat"
1. Run Installer
2. Copy patch.exe to InstalledDir

2a. ChDir "InstalledDir\" 'Change to the correct directory in VB

3. Shell ("InstalledDir/patch.exe")
If you just need to be able to run the patch from a specific working directory, try:

    Public Const SW_SHOWDEFAULT As Integer = 10

    Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
                (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
                 ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

    '...

    Sub RunPatch()
        ShellExecute 0, vbNullString, _
                           "x:\pathto\patch_to_run.exe", vbNullString, _
                           "x:\pathto\InstalledDir", SW_SHOWDEFAULT
    End Sub

Although, personally, I would store the installation directory of the app in a registry key and make either the patch or the patch loader reference it.

HTH

J.
If you want to change to current directory from your bat, then

cd %~dp0
Avatar of nyclio
nyclio

ASKER

Thank you Jim, ShellExecute does work; however, is there a way to pause the execution of my app until patch.exe is finished running?
yes you can run a

Function ShellAndWait(FileName As String)
    Dim objScript
    On Error Goto ERR_OpenForEdit
    Set objScript = CreateObject("WScript.Shell")
    ' Open a file for editing in Notepad and
    '     wait for return.
    'The second parameter (after the FileNam
    '     e) is the Display Mode (normal w/focus,
    'minimized...even hidden. For more info
    '     visit:
    'http://msdn.microsoft.com/scripting/win


    '     dowshost/doc/wsMthRun.htm
        ' The third parameter is the "Wait for r
        '     eturn" parameter. This should be set to
        ' True for the Wait.
        ShellApp = objScript.Run(FileName, 1, True)
        ShellAndWait = True
        EXIT_OpenForEdit:
        Exit Function
        ERR_OpenForEdit:
        MsgBox Err.Description
        Goto EXIT_OpenForEdit
    End Function
But what I would really suggest is using http://nsis.sf.net  it is an open source installer application which can do all of that and even run it for you.
Avatar of nyclio

ASKER

If I do ShellandWait, it defeats the purpose since I can't specify the install directory; i need to find a way to do it ShellExecute...unless I am doing something wrong?
I suppose an alternative is to use ShellExecuteEx to run your process instead, then polling the process until it quits with GetExitCodeProcess.  Something like:

      Public Const SEE_MASK_NOCLOSEPROCESS = &H40
      Public Const SW_SHOWDEFAULT As Integer = 10

      Public Type SHELLEXECUTEINFO
          cbSize As Long
          fMask As Long
          hwnd As Long
          lpVerb As String
          lpFile As String
          lpParameters As String
          lpDirectory As String
          nShow As Long
          hInstApp As Long
          lpIDList As Long
          lpClass As String
          hkeyClass As Long
          dwHotKey As Long
          hIcon As Long
          hProcess As Long
      End Type

      Public Declare Function ShellExecuteEx Lib "shell32.dll" Alias "ShellExecuteEx" (SEI As SHELLEXECUTEINFO) As Long
      Public Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long

      Public Function ShellAndWait(ByVal szFileName As String, ByVal szWorkingDir As String) As Long

            Dim oSEI As SHELLEXECUTEINFO
            Dim hExec As Long
            Dim hProcess As Long
            Dim nExitCode As Long

            With oSEI
                  .cbSize = Len(oSEI)
                  .fMask = SEE_MASK_NOCLOSEPROCESS
                  .hwnd = 0
                  .lpVerb = vbNullChar
                  .lpFile = szFileName
                  .lpParameters = vbNullChar
                  .lpDirectory = szWorkingDir
                  .nShow = SW_SHOWDEFAULT
                  .hInstApp = 0
                  .lpIDList = 0
            End With

            hExec = ShellExecuteEx(oSEI)

            hProcess = oSEI.hProcess

            Do Until GetExitCodeProcess(hProcess, nExitCode)
                  'DO NOTHING UNTIL ABOVE API RETURNS A VALUE
            Loop

            ShellAndWait = nExitCode

      End Function

      Public Sub RunPatch()
            Dim nExitCode As Long

            MsgBox "Starting process"
            nExitCode = ShellAndWait("x:\pathto\patch_to_run.exe", "x:\pathto\install_dir")
            MsgBox "Finished with exit code " & nExitCode
      End Sub

should do it...

HTH

J.
Also above, where it says:

    'DO NOTHING UNTIL ABOVE API RETURNS A VALUE

you can put the line:

    DoEvents

This will allow Windows to process messages and prevent your program from appearing unresponsive.  If you have an event-driven app (like a form), though, it will also allow users to do things on that form while it is running (which may, obviously, negate the desired effect).  Otherwise, if you are running a form, you may want to change the mouse cursor to the hourglass to indicate to the user that activity is taking place.

J.
Avatar of nyclio

ASKER

It doesn't wait; it just says Finished with Exit Code 259 and then moves to the next step.
Hi,

Dim StrComputer
Dim objWMIService
Dim colProcesses
Dim objProcess
Dim BogusID
Dim errResult
Dim I

Const FileName As String = "C:\windows\system32\calc.exe"

StrComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & StrComputer & "\root\cimv2:Win32_Process")
errResult = objWMIService.Create(FileName, Null, Null, BogusID)
Set objWMIService = GetObject("winmgmts:\\" & StrComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecNotificationQuery("Select * From __InstanceDeletionEvent " & "Within 1 Where TargetInstance ISA 'Win32_Process'")
Do Until I = 999
    Set objProcess = colProcesses.NextEvent
    If objProcess.TargetInstance.ProcessID = BogusID Then
        Exit Do
    End If
Loop

MsgBox "Install finished"
>> It doesn't wait; it just says Finished with Exit Code 259 and then moves to the next step.

OK, instead of:

          Do Until GetExitCodeProcess(hProcess, nExitCode)
               'DO NOTHING UNTIL ABOVE API RETURNS A VALUE
          Loop

try:

          Public Const INFINITE = &HFFFF
          Declare Function WaitForSingleObject Lib "kernel32" Alias "WaitForSingleObject" _
                    (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long          
          Declare Function CloseHandle Lib "kernel32" Alias "CloseHandle" (ByVal hObject As Long) As Long
          '...
          '...
          WaitForSingleObject hProcess, INFINITE
          CloseHandle hProcess

J.
Avatar of nyclio

ASKER

Is this how it should look?  Because if so, it is still doing the same thing.  I appreciate all the help by the way thanks.

Public Function ShellAndWait(ByVal szFileName As String, ByVal szWorkingDir As String) As Long

          Dim oSEI As SHELLEXECUTEINFO
          Dim hExec As Long
          Dim hProcess As Long
          Dim nExitCode As Long

          With oSEI
               .cbSize = Len(oSEI)
               .fMask = SEE_MASK_NOCLOSEPROCESS
               .hwnd = 0
               .lpVerb = vbNullChar
               .lpFile = szFileName
               .lpParameters = vbNullChar
               .lpDirectory = szWorkingDir
               .nShow = SW_SHOWDEFAULT
               .hInstApp = 0
               .lpIDList = 0
          End With

          hExec = ShellExecuteEx(oSEI)

          hProcess = oSEI.hProcess

Do Until GetExitCodeProcess(hProcess, nExitCode)
          WaitForSingleObject hProcess, INFINITE
          CloseHandle hProcess
Loop
           ShellAndWait = nExitCode

End Function
Have you tried rbgCODE 's method

Changed the line to

ShellAndWait = objScript.Run(FileName, 1, True)

ShellAndWait will contain the return code from running task



I found ShellExecuteEx may not work for all the cases.

ie

try on wordpad.exe
"C:\Program Files\Windows NT\Accessories\wordpad.exe"

it doesn't work
Avatar of nyclio

ASKER

Yes I have, this way does not allow me to set the default directory i want the program to run from.  Is there a way to set the default dir. using CreateProcessA?
ShellExecute works fine, atleast the snippet I have, but I would use WMI since it doesn't effect the painting of the application when in conjuction for WaitForSingleObject
I am actually still confused as to what your goal is nyclio, There are alot of good examples of shelling out an application and waiting until that application is terminated to proceed with the next line of code.
Avatar of nyclio

ASKER

I figured it out.  Using this function, the 8th argument in CreateProcessA is to set the Default Directory.  This function shells the program, and waits for it to run:



 Public Function ExecCmd(cmdline$)
      Dim proc As PROCESS_INFORMATION
      Dim start As STARTUPINFO

      ' Initialize the STARTUPINFO structure:
      start.cb = Len(start)

      ' Start the shelled application:
      ret& = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
         NORMAL_PRIORITY_CLASS, 0&, defaultdirectory$, start, proc)

      ' Wait for the shelled application to finish:
         ret& = WaitForSingleObject(proc.hProcess, INFINITE)
         Call GetExitCodeProcess(proc.hProcess, ret&)
         Call CloseHandle(proc.hThread)
         Call CloseHandle(proc.hProcess)
         ExecCmd = ret&
   End Function
Can you confirm if that snippet re-paints correctly? Meaning when you move the application around does it turn white in areas on your screen, WaitforSingleObject is okay if your not displaying and interface
ASKER CERTIFIED SOLUTION
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of nyclio

ASKER

Thanks for the input and help everyone!