Running batch file from Excel VBA - vba

I have a batch file (Windows command line) that creates a VBS file, runs it, and that in turn re-launches the cmd.exe program in elevated mode so that I can copy a file to the System32 folder.
If I run this batch file manually, all goes according to plan, but if I run it from a VBA Excel macro the batch file gets stuck in an infinite loop, even after the UAC prompt has appeared and I have allowed elevated access. Am I calling the batch file incorrectly from VBA?
I'm fine with VBA, OKish with windows CMD, but haven't often used the two together.
I've tried the following statements in my macro, all 3 have the same result:
Shell Environ("USERPROFILE") & "\Desktop\TEMP.bat"
CreateObject("WScript.Shell").Run "CMD /C %USERPROFILE%\Desktop\TEMP.bat", 0, False
Debug.Print CreateObject("WScript.Shell").Exec("CMD /C %USERPROFILE%\Desktop\TEMP.bat").StdOut.ReadAll
For what it's worth, this is the Batch file code:
#ECHO OFF
:----------------------
>NUL 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
IF '%ERRORLEVEL%' NEQ '0' (
GOTO UACPrompt
) ELSE ( GOTO gotAdmin )
:UACPrompt
ECHO Set UAC = CreateObject^("Shell.Application"^) > "%TEMP%\getadmin.vbs"
ECHO UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%TEMP%\getadmin.vbs"
"%TEMP%\getadmin.vbs"
EXIT /B
:gotAdmin
IF EXIST "%temp%\getadmin.vbs" ( DEL "%TEMP%\getadmin.vbs" )
PUSHD "%CD%"
CD /D "%~dp0"
:----------------------
:: < REST OF SCRIPT HERE - NOT RELEVANT TO QUESTION >
And this is what's in the VBS file:
Set UAC = CreateObject("Shell.Application")
UAC.ShellExecute "C:\Users\SO\Desktop\TEMP.bat", "", "", "runas", 1
Any pointers/suggestions welcome. Thanks in advance.

I had faced the same problem, and below code solved it. Howerver, the problem is Shell doesn't have bWaitOnReturn option.
Dim Foldername As String
Foldername = "C:\WorkspaceRFT\RMS\command.bat"
Shell "C:\WINDOWS\explorer.exe """ & Foldername & "", vbNormalFocus

Related

vba: execute shell after finishing previous shell

How to call the second shell in this case after pressing pause?
Shell "cmd /c cd %tmp% && echo hello > tmpfile && pause", 1
Shell "cmd /c cd %tmp% && echo hello > tmpfile && pause", 1
and in this case
Shell "cmd /c cd %tmp% && echo hello > tmpfile", 0
Shell "cmd /c cd %tmp% && echo hello > tmpfile", 0
The Shell command you're using is asynchronous meaning it can run more than one process at a time, so in the case of your two Shell statements in a row, both will be executed simultaneously.
An alternative way is to instead use the Run command of Windows Script Host (WScript.Shell) since it has more options including one to wait for execution of the program to finish before it continues.
Sub ShellWait(fName As String, Optional showWindow As Boolean = True)
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
wsh.Run fName, -showWindow, True
End Sub
You can also hide the window completely by specifying False as the second parameter. (Caution with that option if user input is required!)
For example:
Sub demo()
ShellWait "x:\test.bat"
Beep
MsgBox "Finished running!"
End Sub
More Information:
Chip Pearson : Shell and Wait
Stack Overflow : Wait for shell command to complete
Stack Overflow : Wait for Shell to finish, then format cells
DevGuru : WSH » wshshell » Run

Delete all files with extension .c using shell command in vba

i want to delete all files with extension ".c" in a folder from shell command in vba, below code i am not able to execute in VBA Macro. should there be any issue if folder name contains spaces in it or what changes should be done in code
all_C_Files = Selected_User_Output_Folder & "*.C"
Shell "cmd /c del /F" & all_C_Files
'Selected_User_Output_Folder = "C:\Users\Berater\Desktop\Config File Generator"
Why to use shell command at all when you can use Kill
Sub test()
Selected_User_Output_Folder = "C:\Users\Berater\Desktop\Config File Generator\*.c"
On Error Resume Next
Kill Selected_User_Output_Folder
End Sub
Always good practice to quote file/folder paths:
all_C_Files = Selected_User_Output_Folder & "*.C"
Shell "cmd /c del /F """ & all_C_Files & """"

How can I run more then too commands with WScript.Shell object?

I'm using below macro in goal of obtaining list of all files in folder :
Sub SO()
Const parentFolder As String = "C:\Users\bloggsj\folder\" '// change as required, keep trailing slash
Dim results As String
results = CreateObject("WScript.Shell").Exec("CMD /C DIR """ & parentFolder & "*.*"" /S /B /A:-D").StdOut.ReadAll
Debug.Print results
Ens Sub
but it gives me invalid output as it doesn't chandle Unicode characters, which are part of files names in my directory. In normal batch file I could use additional command 'CHCP 1250' to change coding page for symbols. But I can't incorpotrate it into above macro. I've tried in several ways like :
results = CreateObject("WScript.Shell").Exec("CMD /C CHCP 1250 DIR """ & parentFolder & "*.*"" /S /B /A:-D").StdOut.ReadAll
and
results = CreateObject("WScript.Shell").Exec("CMD /C ""CHCP 1250"" ""DIR """ & parentFolder & "*.*"" /S /B /A:-D""").StdOut.ReadAll
Ampersand
command1 & command2 : Use to separate multiple commands on one command
line. Cmd.exe runs the first command, and then the second command.
CMD /C CHCP 1250 & DIR ....
However VBA has native support for a directory listing and VBScript can use the FileSystemObject to achieve the same.

Sending multiple commands from Visual Basic to cmd

So I have batch file that contains series of commands. (about 10) -
I've decided that I would like to create simple gui for this tool - simply one button that would execute commands one after another (when command A is finished command B starts)
Thing is that I do not want to use SendKey method, I am looking for a way that would not even show command prompt window. Is there any way to achieve this?
I am obviously beginner, so any help is welcome :).
Thank you very much!
This is part of the batch file that I somehow need to "translate" to VBA code.
wevtutil epl Application %temp%\Sysinfo\AppLog.evtx
wevtutil epl System %temp%\Sysinfo\SystemLog.evtx
takeown /f %temp%\Sysinfo /r /d y
icacls %temp%\Sysinfo /grant administrators:F /T
set FILETOZIP=%temp%\Sysinfo
set TEMPDIR=%temp%\Zip
rd /s /q %TEMPDIR%
mkdir %TEMPDIR%
copy %FILETOZIP% %TEMPDIR%
echo Set objArgs = WScript.Arguments > _zipIt.vbs
echo InputFolder = objArgs(0) >> _zipIt.vbs
echo ZipFile = objArgs(1) >> _zipIt.vbs
echo CreateObject("Scripting.FileSystemObject").CreateTextFile(ZipFile, True).Write "PK" ^& Chr(5) ^& Chr(6) ^& String(18, vbNullChar) >> _zipIt.vbs
echo Set objShell = CreateObject("Shell.Application") >> _zipIt.vbs
echo Set source = objShell.NameSpace(InputFolder).Items >> _zipIt.vbs
echo objShell.NameSpace(ZipFile).CopyHere(source) >> _zipIt.vbs
echo wScript.Sleep 2000 >> _zipIt.vbs
CScript _zipIt.vbs %TEMPDIR% %userprofile%\Desktop\Systeminfo.zip
CreateObject("Wscript.Shell").Run """" & WScript.Arguments(0) & """", 0, False
Above runs anything passed on the command line in a hidden window.
& seperates commands on one line
cmd /c dir&time /t%date /t%type c:\windows\win.ini&echo hello

How to capture the PID of a process when launching it from command line?

Is there a way to do this purely in a .bat file?
The purpose is to launch iexplore.exe, then kill just that instance when it's finished.
Here's what I use:
#echo off
rem there is a tab in the file at the end of the line below
set tab=
set cmd=javaw -jar lib\MyProg.jar
set dir=%~dp0
echo Starting MyProg
set pid=notfound
for /F "usebackq tokens=1,2 delims=;=%tab% " %%i in (
`wmic process call create "%cmd%"^, "%dir%"`
) do (
if /I %%i EQU ProcessId (
set pid=%%j
)
)
echo %pid% > MyProg.pid
The directory is set to the directory that the cmd file is located in. Change dir to alter that. Modify cmd to change which command is run.
If you want a stop.cmd that kills it, it would look like this
#echo off
for /F %%i in (%~dsp0MyProg.pid) do taskkill /F /PID %%i
del %~dsp0MyProg.pid
you can use vbscript, here's an example creating notepad, then terminating it using its pid
strComputer = "."
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
Set objConfig = objStartup.SpawnInstance_
Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
errReturn = objProcess.Create("notepad.exe", null, objConfig, PID)
If errReturn = 0 Then
WScript.Echo "Process ID is: " & PID
End If
WScript.Echo "Ready to kill process: " & PID & "? [Y|y]"
Do While Not WScript.StdIn.AtEndOfLine
strInput = strInput & WScript.StdIn.Read(1)
Loop
If LCase(strInput) = "y" Then
WScript.Echo "Select * from Win32_Process Where ProcessId = '" & PID & "'"
Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where ProcessId = '" & PID & "'")
For Each objProcess in colProcessList
objProcess.Terminate()
Next
End If
save as myscript.vbs and on command line
c:\test> cscript /nologo myscript.vbs
A slight variation on the answer provided by #kybernetikos since it has a parsing issue. Note the line if %%j gr 0 (
#echo off
rem there is a tab in the file at the end of the line below
set tab=
set cmd=javaw -jar lib\MyProg.jar
set dir=%~dp0
echo Starting MyProg
set pid=notfound
for /F "usebackq tokens=1,2 delims=;=%tab% " %%i in (
`wmic process call create "%cmd%"^, "%dir%"`
) do (
if %%j gtr 0 (
set pid=%%j
)
)
echo %pid% > MyProg.pid
Most often you do know what task you start - in this case, which page iexplorer shall show.
So how about
taskkill /FI "Windowtitle eq *yourpagetitle*"
It will kill all instances of something showing your page title, but with a specific title most often there should be exactly one.
Tom
Ummm, TaskList & TaskKill?!
For some reason your approach of getting process id did not work for me, but since I'm expert in batches, I've coded my own approach, attaching here:
#echo off
call:AsyncCmd
rem call:AsyncCmd "echo hello world"
rem call:AsyncCmd "call build.bat"
exit /b
rem ------------------------------------------------------------------------------------------
rem Starts asynchronous command execution
rem %1 is command, if empty - only aborts existing build.
rem ------------------------------------------------------------------------------------------
:AsyncCmd
if exist %~dp0SetupBuild_Completed.txt (
del /f %~dp0SetupBuild_Pid.txt >nul 2>&1
del /f %~dp0SetupBuild_Completed.txt >nul 2>&1
)
if not exist %~dp0SetupBuild_Pid.txt goto lStartProc
rem --------------------------------------------------
rem Abort build process
rem --------------------------------------------------
set /p pid=<%~dp0SetupBuild_Pid.txt
echo Cancelling setup build process, process id %pid%
pskill -t %pid%
del /f %~dp0SetupBuild_Pid.txt >nul 2>&1
:lStartProc
if "%~1" == "" exit /b 0
rem --------------------------------------------------
rem Starts asyncronous build process
rem --------------------------------------------------
set dir=%~dp0
set dir=%dir:~0,-1%
for /f "tokens=2 delims==; " %%a in ('wmic process call create "cmd /c mincon.exe && %~1 && echo 1>%~dp0SetupBuild_Completed.txt"^, "%dir%" ^| findstr /r "ProcessId"') do set pid=%%a
echo Setup build started, process id: %pid%
echo %pid%>%~dp0SetupBuild_Pid.txt
exit /b 0
PowerShell can be used for this:
powershell -executionPolicy bypass -command "& {$process = start-process $args[0] -passthru -argumentlist $args[1..($args.length-1)]; exit $process.id}" notepad test.txt
echo Process ID of new process: %errorlevel%
I think you can't do that with simple command line utilities, as IE actually spawns child processes for each tab, i.e. if IE is not yet running you would get one parent IE process and a child process for the tab, and if IE is already running you would simply get a single child process.
It will be even quite tricky when you write your own tool to kill IE because when you kill a child (tab) process, IE will automatically recover this tab.
See also this related question: How to obtain process of newly created IE8 window? (though there is no good answer there).