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!
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!
1. Run Installer
2. Copy patch.exe to InstalledDir
2a. ChDir "InstalledDir\" 'Change to the correct directory in VB
3. Shell ("InstalledDir/patch.exe")
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.ex e", 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.
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.ex
"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
cd %~dp0
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.Shel l")
' 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
Function ShellAndWait(FileName As String)
Dim objScript
On Error Goto ERR_OpenForEdit
Set objScript = CreateObject("WScript.Shel
' 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.
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(hProces s, 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\pa tch_to_run .exe", "x:\pathto\install_dir")
MsgBox "Finished with exit code " & nExitCode
End Sub
should do it...
HTH
J.
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(hProces
'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\pa
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.
'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.
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(FileN ame, Null, Null, BogusID)
Set objWMIService = GetObject("winmgmts:\\" & StrComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecNotifica tionQuery( "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"
Dim StrComputer
Dim objWMIService
Dim colProcesses
Dim objProcess
Dim BogusID
Dim errResult
Dim I
Const FileName As String = "C:\windows\system32\calc.
StrComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & StrComputer & "\root\cimv2:Win32_Process
errResult = objWMIService.Create(FileN
Set objWMIService = GetObject("winmgmts:\\" & StrComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecNotifica
Do Until I = 999
Set objProcess = colProcesses.NextEvent
If objProcess.TargetInstance.
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(hProces s, 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.
OK, instead of:
Do Until GetExitCodeProcess(hProces
'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.
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(hProces s, nExitCode)
WaitForSingleObject hProcess, INFINITE
CloseHandle hProcess
Loop
ShellAndWait = nExitCode
End Function
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(hProces
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
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
ie
try on wordpad.exe
"C:\Program Files\Windows NT\Accessories\wordpad.exe
it doesn't work
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.
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(vbNullStrin g, cmdline$, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, defaultdirectory$, start, proc)
' Wait for the shelled application to finish:
ret& = WaitForSingleObject(proc.h Process, INFINITE)
Call GetExitCodeProcess(proc.hP rocess, ret&)
Call CloseHandle(proc.hThread)
Call CloseHandle(proc.hProcess)
ExecCmd = ret&
End Function
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(vbNullStrin
NORMAL_PRIORITY_CLASS, 0&, defaultdirectory$, start, proc)
' Wait for the shelled application to finish:
ret& = WaitForSingleObject(proc.h
Call GetExitCodeProcess(proc.hP
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thanks for the input and help everyone!
CD installedDir
Patch.exe
and then Shell "MyBat.bat"