Is it possible to return error code to VBA from batch file? - vba

My batch file:
SET username=%1
SET password=%2
net use "\\gessel\toys\name" %password% /user:shops\%username%
ECHO.%ERRORLEVEL%
:copy
Xcopy "\\gessel\toys\name\babytoys" "%appdata%\shops" /S /E
ECHO.%ERRORLEVEL%
IF ERRORLEVEL 0 goto disconnect
goto end
:disconnect
net use "\\gessel\toys\name\babytoys" /delete
goto end
:end
EXIT /B %ERRORLEVEL%
I have called above batch file from VBA and the code as follows:
call Shell(Environ$("COMSPEC") & " /c " & path & username & password, vbHide)
The above code is works fine. But I need to validate whether the files are copied or not in the VBA. Suppose the customer entered his username and password wrongly then he won't get the toys info. Then I have to display a message box that display message as "entered information wrong". So for that I have tried the code like this:
sub submit_click
Dim as error as integer
error = Shell(Environ$("COMSPEC") & " /c " & path & username & password, vbHide)
if error <> 0
MsgBox "Provided info wrong", vbOKOnly, "Failure"
end if
end sub
But the above code does not work. It always returns the value even the username and password is correct. But if I run the batch file it correctly returns value such as for correct details 0 and for wrong data is 2 or 4. Please anyone help me to capture error code from batch file and to pass it into VBA.

The value of ERRORLEVEL variable changes with each command execution (more or less). So as the code in your batch file is executing, each command generates a change. You need to store the value for later processing or, in your case, exit with the adecuated value in each of the steps:
SET "username=%~1"
SET "password=%~2"
rem This will be tested for a errorlevel value of 1 or greater. In this case,
rem no further processing will be done if errors found, so we return our own
rem value to the calling process
net use "\\gessel\toys\name" %password% /user:shops\%username%
if errorlevel 1 exit /b 100
rem We are connected, and will disconnect no matter if xcopy worked or failed
rem So, the result of the command will be stored to later return the adecuate value
Xcopy "\\gessel\toys\name\babytoys" "%appdata%\shops" /S /E
set "exitCode=%errorlevel%"
net use "\\gessel\toys\name\babytoys" /delete
EXIT /B %exitCode%
Now, in the vba code the error variable can be tested for value 100 (what we returned from errors in net use) or for any of the values returned from xcopy.
Now that we have a working batch file, let's to to Excel.
NO, Shell function in VBA can not do what you are asking. The return value of Shell is the id of the running process. Shell do not wait for the process to end, so, it can not return its exit code.
BUT, the WshShell object can do what you need.
Dim oSHELL, batchFile, user, password, exitCode
Set oSHELL = VBA.CreateObject("WScript.Shell")
batchFile="myBatchFile.cmd"
user="myUserName"
password="this is my password"
' Quote strings to avoid problems with spaces
' The arguments are WhatToRun, WindowStyle (0=hide), WaitForProcessToEnd
exitCode = oSHELL.Run(""""+batchFile+""" """+user+""" """+password+"""", 0, True)

Related

Batch file to upgrade version of an MS database prior to opening it (Microsoft Access 2016 and Microsoft Access 365)

I have a front end called PMD_FE.accdb with a table called tbl_Version. It only contains a unique field called "VERSION." PMD_FE.accdb, lives locally on everyone's machine at C:\users\public\PMDTools\PMD_FE.accdb IN PMD_FE.accdb, there's also a link to the same table TBL_VERSION, but this is the master copy which exists on the server. The name of that linked table is tbl_Version_Latest. I'm trying to write a batch script that will check tbl_Version_Latest and compare it with tbl_Version, if they match, the batch script simply launches PMD_FE.accdb from the Local Home: C:\Users\Public\PMDTools\PMD_FE.accdb. If tbl_Version_Latest is different, it downloads the file from the server which is S:\PMDTools\04_FrontEnd\PMD\PMD_FE.accdb, to C:\Users\Public\PMDTools\ prior to launching it. I have 3 public subroutines in PMD_FE.accdb:
Public Sub DownloadLatestVersion()
dim SharedAppHome as string
SharedAppHome = "S:\PMDTools\04_frontEnd\PMD\"
Shell "xcopy " & SharedAppHome & " " & CurrentProject.Path & "\PMD_FE.accdb /Y"
Debug.Print Shell("echo %errorlevel%", vbNormalFocus)
End Sub
Public Sub GetLocalVersionNumber()
Dim wsh As Object
Set wsh = CreateObject("WScript.Shell")
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("tbl_Version")
rs.MoveLast
wsh.PopUp rs![VERSION]
Set rs = Nothing
Set db = Nothing
End Sub
Public Sub GetLatestVersionNumber()
Dim wsh As Object
Set wsh = CreateObject("WScript.Shell")
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("TBL_VERSION_LATEST", dbOpenDynaset, dbSeeChanges)
rs.MoveLast
wsh.PopUp rs![VERSION], 0, "Latest Version", vbOKOnly
Set rs = Nothing
Set db = Nothing
End Sub
The batch script "LaunchPMD.bat" seems straightforward,
#echo off
rem Set the local file path
set localFile=C:\Users\Public\PMDTools\PMD_FE.accdb
rem Check if the local file exists
if not exist %localFile% (
rem If the local file does not exist, download the latest version from the server
echo Downloading latest version
"C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE" %localFile% /x DownloadLatestVersion
) else (
rem If the local file does exist, compare the version numbers of the local and latest versions
for /f "tokens=2 delims==" %%a in ('"C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE" %localFile% /x GetLatestVersionNumber') do set latestVersion=%%a
for /f "tokens=2 delims==" %%a in ('"C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE" %localFile% /x GetLocalVersionNumber') do set localVersion=%%a
rem If the latest version is newer than the local version, download it
if "%localVersion%" LSS "%latestVersion%" (
echo New version found: %latestVersion%
msaccess.exe %localFile% /x DownloadLatestVersion
)
)
rem Open the local file
echo Opening %localFile%
start "" %localFile%
but even after multiple re-writes I can't get it to work. There are no errors when I run the batch script in the cmd window. The only error thrown at the very end is this:
'GetLatestVersionNumber' is a new macro or macro group, make sure you have saved it and that you have typed its name correctly.
#echo off
rem Set the local file and access executable locations
set "localFile=C:\Users\Public\PMDTools\PMD_FE.accdb"
SET "access=C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE"
set "latestVersion=z"
set "localVersion= "
rem Check whether the local file exists
if exist "%localFile%" (
for /f "tokens=2 delims==" %%b in ('"%access%" "%localFile%" /x GetLatestVersionNumber') do set "latestVersion=%%b"
for /f "tokens=2 delims==" %%b in ('"%access%" "%localFile%" /x GetLocalVersionNumber') do set "localVersion=%%b"
)
IF "%latestVersion%" gtr "%localVersion%" (
echo Downloading latest version: %latestVersion%
"%access%" %localFile% /x DownloadLatestVersion
)
rem Open the local file
echo Opening %localFile%
start "" %localFile%
GOTO :EOF
I have no way of testing this - use caution
I'm assuming that the .vba works.
The fundamental problem is that you wish to ensure that the latest version is downloaded if the current version is not the latest.
I've also modified the code in accordance with the normally-accepted SO standards:
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal backslash or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier. If the syntax set var="value" is used, then the quotes become part of the value assigned.
I prefer to avoid ADFNPSTXZ (in either case) as metavariables (loop-control variables)
ADFNPSTXZ are also metavariable-modifiers which can lead to difficult-to-find bugs
(See `for/f` from the prompt for documentation)
So - setting the local filename and access executable name should be obvious.
Set latestvarsion to a high ASCII value and localversion to a low value. This prepares the code to perform the download.
If the file exists, then set latestvarsion and localversion to their actual values.
Download if the latest > current.
Caution: You have not revealed the format of the version number returned by your vba. Since I've used the string-comparison syntax, the strings will be compared as ASCII strings. In consequence, version "10.3.22" will be seen as earlier than "9.6.3" as 9>1. Reveal the format, and this can be corrected.
Your code's fundamental problem is that within a parenthesised series of instructions (aka "code block") any %var%, including %errorlevel%, is replaced by the then-current ("parse time") value of that variable when the block syntax is being validated. The syntax if [not] errorlevel n may be used, meaning if the CURRENT errorlevel is [not] "n" OR GREATER THAN "n". Otherwise, delayedexpansion and !var! needs to be used to access the current value of the variable, including magic variables like errorlevel time date cd and others. Stephan's DELAYEDEXPANSION link
A simpler and proven method is to just copy the master frontend at each launch using a shortcut.
Update will be automatic, and errors from bloating or corruption of the frontend will be fixed just by a relaunch.
It requires a script to launch the application. That is described in full in my article:
Deploy and update a Microsoft Access application with one click

Command line to kill Adobe Reader process only if it was not already running

I am trying some cmd.exe coding for the first time. I created a registry key, which contains a command called by a custom protocol handler­. What I'm trying to accomplish : Call Adobe Reader which will automatically print a pdf to a given printer. This part already works. I'm using powershell to download the pdf locally from a URL. I also want to kill the Acrobat process once i'm done, but only if it was already opened BEFORE calling my command, so I don't kill the process if the user already has a pdf opened when calling the protocol handler via Chrome.
Excuse the more than ugly formatting, a one-line command is a must in my case, I can't make a batch file or .ps1 file.
cmd /V /C set exists="0" & (tasklist | find "AcroRd32.exe" > nul) & if
(%%ERRORLEVEL%%==0 set exists="1") & set myvar=%1 & call set
myvar=%%myvar:ie:=%% & call set printer=%%myvar:*###=%% & call set
printer=^"%%printer%%^" & call set printer=%%printer:'=%% & call start
powershell.exe -ExecutionPolicy Bypass -Command $url=%%myvar%%; $pdf =
$url.split('#'); md -Force c:\TempPdf "| Out-Null"; (New-Object
System.Net.WebClient).DownloadFile($pdf,'C:/TempPdf/PatientLabel.pdf'); &
timeout /t 2 & call start acrord32.exe /n /t C:/TempPdf/PatientLabel.pdf
%%printer%% & timeout /t 2 & (if !exists!=="1" taskkill /f /im acrord32.exe)
The part which does not work is the tasklist/taskkill. I want to set the %%exists%% variable to 1 if I can find the process running before starting AcroRd32.exe and kill it after printing if %%exists%%=1. I think this might just not be working because of the execution time/parse, but I don't really know how to use /V and !var! anyway. The exists and ERRORLEVEL variables don't seem to expand.
Thanks for the help !

SAS-code can't scan the parameters from Bat file which came from VBA

I am trying to run my SAS code from my VBA-code by using bat file (batch run). So the system should work like this: First VBA-code sends some parameters to bat file, then the bat file sends this parameters into my SAS code. Then the bat file executes my SAS code.
However, obviously the Bat file can not send the parameters into my SAS-code, they don't exist according to the SAS-system. When I run my VBA-code (which runs also the Bat file), I get an error message like
This window is unavailable in line-mode.
The VBA code is like this:
Public Sub RunSASCodeViaBatFile()
...
Parameter settings here
...
Dim cmdString As String: cmdString = batPath & batFile & " " & SASFilePath & " " & SASFile & " " & SASOutputPath & " " & YearMonth
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 2
wsh.Run cmdString, windowStyle, waitOnReturn
End Sub
The commands in batch file looks like this:
Some General SAS settings in bat file like:
set sas="C:\Program Files\SAS\SASFoundation\9.2(32-bit)\sas.exe" -autoexec
....
Settings for the input parameters
set SASFilePath=%1
set SASFile=%2
set SASOutputPath=%3
set YearMonth=%4
if %debug%==1 (
echo %thisscript%: Debug: Execute SAS program
echo %thisscript%: Debug: sas=%sas%
)
%sas% -sysin "%sasfilepath%\%sasfile%" -SYSPARM "%SASFilePath%#%SASFile%#%SASOutputPath%#%YearMonth%" -log "%SASOutputPath%\log\%SASFile%_%date_time%.log" -nosplash -icon -no$syntaxcheck -rsasuser -noprint
if %debug%==1 echo %thisscript%: Debug: errorlevel=%errorlevel%
if %errorlevel% neq 0 (
echo %thisscript%: Error: Execution of SAS program returned code %errorlevel%
exit /b %errorlevel%
)
And finally the SAS code scans (reads in) the batch parameters like below. I am trying to read batch parameters with SYSPARM command:
%LET sasfilepath = %SCAN(&SYSPARM, 1, '#');
%LET sasfile = %SCAN(&SYSPARM, 2, '#');
%LET sasoutputpath = %SCAN(&SYSPARM, 3, '#');
%LET perdate = %SCAN(&SYSPARM, 4, '#');
I think the problem happens when the SAS code try to scan the parameters batPath, batfile, SASFilePath, SASFile, SASOutputPath, YearMonth. Because the batch file can use these parameters correctly to create log names or to find paths with these parameters.
So anyone has any idea about why my SAS can't scan the bat-parameters?
From this note, looks like your batch SAS job is trying to do something that requires an interactive session, such as using DM commands. http://support.sas.com/kb/44/705.html
The %let statements you showed should not cause this problem. I would look elsewhere in the SAS code.
Actually first I would remove the -icon option on the SAS invocation. That option probably expects an interactive session with windows to minimize.
If you don't see a suspect in your SAS code after that, try brute force debugging. Comment out all of your SAS code and see if it works. Then uncomment half of it and see if it breaks. Repeat.
If by small chance the %let statements do throw this error, please show the parameter values you are passing in SYSPARM.

More than 1 batch file with same variable

I was wondering if it was even possable to have the same variable in 2 different batch programs.
:home
Set /p Name=What is your Name?
Set /p Com=What Command do you wish to use?
If %Com% == #HELP goto msgbox
(MORE IF's ARE USED)
goto msgbox
:msgbox
cls
start msgbox.cmd
Echo Welcome to the MSGBOX PROGRAM %Name%
%Com% Code was typed, here is the Help file
goto Help
:Help
cls
(display's the help for program)
Set /p return=Would you like to return?
If %return%==Y goto home
If %return%==N goto exit
(Set errorlevel==1 if no selection)
:exit
cls
Echo press Enter to exit program
pause >nul
yes.
1) If you call the second bat file from the first , the second will inherit all variables.
2) You can also define environment variable.You can use SETX command
setx variable value
Though it's written in the registry you cannot use it straightforward - it will be accessible in the next cmd sessions.So you'll need temporary variable in the script:
Set /p Com=What Command do you wish to use?
rem making variable accessible for the next run or for another batch
setx Com %com%
And you can override its value for every cmd session or script you want with set.After that it will use the registry value again.
3) And you can also use a temp file, but this will require to read it in the next batch :
Set /p Com=What Command do you wish to use?
echo %com%>c:\command.file
in the next script you'll need to use:
for /f "useback tokens=* delims=" %%# in ("c:\command.file") do set "com=%%#"

Get Currently Logged in username

I am trying to get the logged in username of the user by using VBS.
I tried some codes which works when I run the script directly (Double Clicking it)
Set wshNetwork = CreateObject("WScript.Network")
strUser = wshNetwork.Username
WScript.Echo "Current User: " & strUser
However, what I need to do is to use CMD to run a scheduled task using the AT command.
When it ran, the username would be the computer's name instead of the logged in user.
This problem also occurs when I run CMD as administrator and use wscript to run the script.
Is there any way to bypass this context and get the logged in user instead of the one that runs the script?
The command
query session console
should provide what you need
For an easier to parse
quser console
EDITED - Included sample vbs code
Dim strCmd
strCmd = "cmd /q /c ""quser console | find /i ""console"" "" "
Dim buffer
buffer = WScript.CreateObject("WScript.Shell").Exec(strCmd).StdOut.ReadAll()
Dim c, consoleUserName
c = InStr(buffer,"console")
If c > 2 Then
consoleUserName = Trim(Mid(buffer,2,c-2))
Else
consoleUserName = ""
End If
WScript.Echo consoleUserName
I suggest you execute the command
set
from the prompt. It may reveal a few items that are set in the environment that may be of interest.
set user
will censor the list so that only variables that start user will be displayed.