Batch Programming, Error Handling, and Start Command - error-handling

I am just starting to learn how to script. I'm trying to understand how the system handles Error Levels and how they can be used in error handling. I know there is a difference between the environment variable %ERRORLEVEL% and the Error Level of the system. If I understand this correctly, then the
If ERRORLEVEL 1
code would check the environment variable before it checks the error level of the previous command.
So, in my program I am trying to interface a startup/stop script that will start/stop all scripts of a given machine (for testing I'm just using one application notepad.exe as an example). I have two wrapper scripts that will either start up or stop the applications by passing arguments to the independent script. If there is an error in the independent script, it will set the errorlevel using the
EXIT /B n
command. Once control is returned to the calling script, it will go to an error handling script if the exit status is non-zero.
At first I was setting the %ERRORLEVEL% to zero manually and then testing for an error after a START or TASKKILL command. But then I read that clearing %ERRORLEVEL% with
SET ERRORLEVEL=
is a better method. My issue comes in when I try to start the app with
START "" notepad.exe
Whenever I test the errorlevel after this command it is always greater than or equal to 1 unless I use SET ERRORLEVEL=0 before I run the start command. I have inserted the code for the four scripts below. Any insight and advice would be greatly appreciated.
appstart.bat:
#echo off
:: Script for application Start
set ERRORLEVEL=
:: ****
:: Additional Batch files will be executed from within this file
:: Example:
:: Call Appbat01.bat
:: The called batch file should set ERRORLEVEL non-zero if error
:: ****
call test.bat -start
if ERRORLEVEL 1 (call error.bat)
echo.
echo Control was returned to appstart.bat...
:: **** End Calls
goto end
:end
appstop.bat:
#echo off
:: Script for application Start
set ERRORLEVEL=
:: ****
:: Additional Batch files will be executed from within this file
:: Example:
:: Call Appbat01.ba
:: The called batch file should set ERRORLEVEL non-zero if error
:: ****
call test.bat -stop
if ERRORLEVEL 1 (call error.bat)
echo.
echo Control was returned to appstop.bat...
:: **** End Calls
goto end
:end
test.bat:
#echo off
if "%1"=="-start" goto :start
if "%1"=="-stop" goto :stop
goto wrongParams
:start
::****
:: Insert start up stripts here...
:: If there is an error, set ERRORLEVEL=1
::****
set ERRORLEVEL=0
echo.
echo ********
echo starting the service...
echo.
::start "" "C:\Program Files\Microsoft Office\office11\winword.exe"
start notepad.exe
if ERRORLEVEL 1 goto error
qprocess notepad.exe
echo *Start.success* ERRORLEVEL is: %ERRORLEVEL%
echo.
goto end
:stop
::****
:: Insert stopping stripts here...
:: If there is an error, set ERRORLEVEL>1
::****
set ERRORLEVEL=0
echo.
echo ********
echo stopping the service...
echo.
qprocess notepad.exe
taskkill /f /im notepad.exe
if ERRORLEVEL 1 goto noProcess
goto end
:noProcess
set ERRORLEVEL=2
echo *noProcess* ERRORLEVEL is now: %ERRORLEVEL%
echo.
exit /b 2
:error
:: Errorhandler. Log application status and cause of error here. Set
:: ERRORLEVEL > 1 before returning to caller.
set ERRORLEVEL=1
echo.
echo **** Error handler inside test.bat ****
echo.
echo *error* ERRORLEVEL is now: %ERRORLEVEL%
echo.
exit /b 1
:wrongParams
:: Output an error if the wrong parameters were passed to this script.
:: Maybe try to self correct the parameter...
set ERRORLEVEL=1
echo.
echo '%1' is an invalid parameter.
echo Usage: %0 [-stop ^| -start]
echo *wrongParams* ERRORLEVEL is now: %ERRORLEVEL%
echo.
exit /b 1
:end
error.bat:
#echo off
echo **** You have reached error.bat ****
echo ERRORLEVEL inside of error.bat is: %ERRORLEVEL%
echo.
::*** Handle error...***
goto error%ERRORLEVEL%
:error2
echo The process could not be stopped for some reason.
goto end
:error1
echo The process had an error in start up.
::*** ***
goto end
:end

You should never SET the %errorlevel% variable. You are correct that there is a difference; The errorlevel that you get from an exiting process is an internal register that you can read with the %errorlevel% syntax. However, if you create a variable named ERRORLEVEL, it will mask the internal register and you lose access to the exit codes.
If you need to set the errorlevel register to a specific value, you can do it with the following command:
%comspec% /c exit %value%
This will spawn a process which immediately exits with the desired code.

Related

Renaming the current process of a ClickOnce application programmatically

I have a ClickOnce application called "Ulti", and I need to run three of this application at once due to my project requirements. During the course of the application, there is a possibility that one or more copies of the application will close due to certain requirements being met.
I have a .bat file scheduled to run every 10 minutes; if not all three copies of the application are running, it should open another instance of the application. However because all three processes of the application are all named "Ulti", my .bat file is unable to differentiate whether or not there are 3 copies of it running, and if not which one is shut down.
As such, is it possible to rename the process of a ClickOnce application dynamically via code at runtime?
You can't rename a process name in runtime. But if your bat file is processes starter you can pass some "Fake" parameter like "1", "2" etc, to your application. It hasn't any effect for your application, but you can differentiate your process by parse command line.
To get process command line parameters you can use WMIC /?. Here is an example:
#echo off
SET PROCESSNAME=notepad.exe
SET PROCESSDIR=C:\Windows
SET PROCESSFULLPATH=%PROCESSDIR%\%PROCESSNAME%
SET WMIC=C:\Windows\System32\wbem\WMIC.exe
SET PID1=1
SET PID2=2
SET PID3=3
REM Prepare
taskkill /IM notepad.exe
CLS
REM 3 Fake notepads
start /b /d "%PROCESSDIR%" %PROCESSNAME% %PID1%
start /b /d "%PROCESSDIR%" %PROCESSNAME% %PID2%
start /b /d "%PROCESSDIR%" %PROCESSNAME% %PID3%
echo You have a time for manual kill some notepad
pause
REM Restart if not founded 1 or 2 or 3 process.
REM 2nd loop doesn't working
REM Process 1
SET PROCESSID=%PID1%
SET KILLED=1
FOR /F "tokens=2" %%x IN ('%WMIC% /OUTPUT:STDOUT process where Name^="%PROCESSNAME%" get Commandline ^|find "%PROCESSNAME%"') DO (
IF "%%x"=="%PROCESSID%" (
SET KILLED=0
)
)
IF %KILLED%==1 (
echo Process %PROCESSID% killed. Restarting...
REM Restart dead copy with %KILLED% index
start /b /d "%PROCESSDIR%" %PROCESSNAME% %PROCESSID%
)
REM Process 2
SET PROCESSID=%PID2%
SET KILLED=1
FOR /F "tokens=2" %%x IN ('%WMIC% /OUTPUT:STDOUT process where Name^="%PROCESSNAME%" get Commandline ^|find "%PROCESSNAME%"') DO (
IF "%%x"=="%PROCESSID%" (
SET KILLED=0
)
)
IF %KILLED%==1 (
echo Process %PROCESSID% killed. Restarting...
REM Restart dead copy with %KILLED% index
start /b /d "%PROCESSDIR%" %PROCESSNAME% %PROCESSID%
)
REM Process 3
SET PROCESSID=%PID3%
SET KILLED=1
FOR /F "tokens=2" %%x IN ('%WMIC% /OUTPUT:STDOUT process where Name^="%PROCESSNAME%" get Commandline ^|find "%PROCESSNAME%"') DO (
IF "%%x"=="%PROCESSID%" (
SET KILLED=0
)
)
IF %KILLED%==1 (
echo Process %PROCESSID% killed. Restarting...
REM Restart dead copy with %KILLED% index
start /b /d "%PROCESSDIR%" %PROCESSNAME% %PROCESSID%
)

Batch scripting for executing SQL scripts in a directory

I want to develop a batch script which will execute each .sql SQL scripts present in the folder where the batch file is placed to and record the logs to a filename_sqloutput.txt file .
The condition is : If any script gives any error message like column name incorrect , or table name etc. The script execution should stop immediately and further scripts should not executed .
I tried with the below code: But its not working as even if the script is giving errors in the output file . The script execution is not stopping ..
Need your help !!!
#echo off
for /f %%a IN ('dir /b *.sql') do (call sqlcmd -S AMRVSP000000318 -i %%a -o"%%~na_sqloutput.txt"
findstr "Msg" %%~na_sqloutput.txt >nul & if %errorlevel% EQU 1 (exit) else (echo Successfully executed %%~na_sqloutput.txt)
)
pause
FINDSTR will set %ERRORLEVEL% as follows:
0 (False) a match is found in at least one line of at least one file.
1 (True) if a match is not found in any line of any file, (or if the file is not found at all).
2 Wrong syntax
An invalid switch will only print an error message in error stream.
Moreover, follow EnableDelayedExpansion article and rewrite the script to more readable structure:
#echo off
SETLOCAL EnableExtensions EnableDelayedExpansion
for /f %%a IN ('dir /b *.sql') do (
call sqlcmd -S AMRVSP000000318 -i %%a -o"%%~na_sqloutput.txt"
findstr "Msg" "%%~na_sqloutput.txt" 2>NUL
if !errorlevel! EQU 0 (
echo error occured %%~na_sqloutput.txt
rem pause to see error
pause
exit /B
) else (
echo Successfully executed %%~na_sqloutput.txt
)
)
pause
.... if errorlevel 1 (exit...
%errorlevel% is the initial value of errorlevel as it stands when the for is encountered.
See endless SO articles about using delayed expansion as another solution.
This syntax means "if the current errorlevel is 1 or greater dothis else dothat"

batch script how to check if variable exist then replace variable with another

I've tried looking every where how to do this but I cant find out how.
How do I check if a variable is defined and if it is replace every in variable in that file with a different variable. thanks
okay here is the code
#echo off
set/p "directoryname= Name of directory (to run on startup type "startup"). : "
if %directoryname%==startup goto startup
:namedirectory
if not exist "%directoryname%" (
mkdir "%directoryname%"
if "!errorlevel!" EQU "0" (
echo Folder created successfully
) else (
echo Error while creating folder
)
) else (
echo Folder already exists
)
goto skip_startup
:startup
:: here i would like to check if the variable %directoryname% is defined,
:: and it is, replace the variable named %directoryname% with
:: (Not changing the variables name) with the directory,
:: "C:\Documents and Settings\All Users\Start Menu\Programs\Startup". that
:: way when ever i can recall my variables in my file i can change my folder
:: to the startup directory.
:skip_startup
cd %directoryname%
:: ^
:: |_ replaced variable
Here is the commented batch code with the requested and some more enhancements:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define as default directory name "startup". The user has to hit just
rem ENTER or RETURN with this default value and variable DirectoryName
rem keeps the default value "startup". Then ask user for directory name
rem and remove all double quotes from the entered directory name.
:EnterDirectory
set "DirectoryName=startup"
echo Please enter the name of the directory.
echo/
echo Hit just RETURN or ENTER to run on startup directory.
echo/
set /P "DirectoryName=Name of directory: "
set "DirectoryName=%DirectoryName:"=%"
rem Check if something other than just one or more double quotes was entered.
rem Prompt user once more after clearing screen if this is really the case.
if not defined DirectoryName cls & goto EnterDirectory
if "%DirectoryName%" == "startup" goto Startup
rem Check if entered directory name does not contain only spaces.
if "%DirectoryName: =%" == "" cls & goto EnterDirectory
echo/
if not exist "%DirectoryName%" (
mkdir "%DirectoryName%"
if errorlevel 1 (
echo Error on creating folder: "%DirectoryName%"
) else (
echo Folder "%DirectoryName%" created successfully.
)
) else (
echo Folder "%DirectoryName%" already exists.
)
goto SetDirectory
rem The variable DirectoryName has the default value "startup". Replace
rem this value by directory path of "Startup" folder in all users start
rem menu of Windows.
:Startup
set "DirectoryName=%ALLUSERSPROFILE%\Start Menu\Programs\Startup"
rem Set current directory to "Startup" directory or entered directory.
:SetDirectory
endlocal & cd /D "%DirectoryName%"
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
cd /?
cls /?
echo /?
endlocal /?
goto /?
if /?
mkdir /?
rem /?
set /?
setlocal /?
See also:
Single line with multiple commands using Windows batch file
DosTips forum topic: ECHO. FAILS to give text or blank line - Instead use ECHO/

Populate a batch from a file

I have been trying to work this 1 out it's a fairly simple batch but I'm pretty much lost, the searches got my close but none seemed to work so I'm hoping somebody can help with this.
I have a batch file, it is far from optimized but basically what I am trying to do now is use the ping I have running in the batch which is written to check.csv should then populate the individual IP's at the beginning (hope that makes sense).
in a nut shell
The variables at the beginning should be populated from check.csv
Here is the code
'#echo off
rem set the individual IP addresses
Rem these are manually set at present, I want these set automatically from check.csv which is created at the end of this file at present but will be moved infront..
set sarah=10.1.14.106
set richard=10.1.15.135
set kathh=10.1.12.79
edited out a number of these to reduce the code on screen for you, but they are all the same format, name & IP
rem set the individual profile name
set n1=sharman
set n2=rburrell
removed more of the same (just duplicates the above code for a different user)
rem set the computer name
set sarahPC=PB7B237
set richardPC=PB1VAL9
removed more of the same
REM set the main install paths
set M3="C:\Users\dclare\Documents\VW Compiler\Installers\M3.exe"
set H3="C:\Users\dclare\Documents\VW Compiler\Installers\H3.exe"
set MooresInst="C:\Users\dclare\Documents\VW Compiler\Catalogues\PD PS\Catalogue"
Rem menu
#echo off
cls
:start
echo.
echo 1 Ping
echo 2 List
echo 3 DBase Installers
echo 4 EXE Installers
echo 5 End
echo.
#CHOICE /C:12345
if errorlevel 5 goto End
if errorlevel 4 goto EXEInstallers
if errorlevel 3 goto DBase
if errorlevel 2 goto List
if errorlevel 1 goto Ping
goto End
:End
Pause
Exit
:EXEInstallers
echo EXE Installers
echo.
echo Upload folder
Xcopy %M3% "S:\# All Public\Information Technology\CAD\VWorlds" /s /y /q
Xcopy %H3% "S:\# All Public\Information Technology\CAD\VWorlds" /s /y /q
echo.
echo %sarah%
Xcopy %M3% "\\%sarah%\c$\Users\%n1%\Desktop\" /s /y /q
Xcopy %H3% "\\%sarah%\c$\Users\%n1%\Desktop\" /s /y /q
echo.
echo %richard%
Xcopy %M3% "\\%richard%\c$\Users\%n2%\Desktop\" /s /y /q
Xcopy %H3% "\\%richard%\c$\Users\%n2%\Desktop\" /s /y /q
echo.
removed more of the same (just duplicates the above code for a different user)
goto Start
:DBase
echo Database Installers
echo.
echo %sarah%
Xcopy %MooresInst% "\\%sarah%\c$\Virtual Worlds\Catalogue" /s /y /q
echo.
echo %richard%
Xcopy %MooresInst% "\\%richard%\c$\Virtual Worlds\Catalogue" /s /y /q
echo.
removed more of the same (just duplicates the above code for a different user)
echo
Goto Start
:List
echo.
echo This is a list of the IP's used currently, check against any that fail.
echo.
echo Name Puter IP
echo sarah %sarahPC%%sarah%
echo richard %richardPC% %richard%
echo kathh %kathhPC% %kathh%
echo amarie %amariePC% %amarie%
removed more of the same (just duplicates the above code for a different user)
echo.
Pause
Goto Start
:Ping
#echo off
if exist "C:\Users\dclare\Documents\VW Compiler\Copy to Desktop\Results.csv" Del /s /q "C:\Users\dclare\Documents\VW Compiler\Copy to Desktop\Results.csv"
Echo Pinging list...
set ComputerList=list.txt
pause
Echo Computername,IP Address>Final.csv
setlocal enabledelayedexpansion
for /f "usebackq tokens=*" %%A in ("%ComputerList%") do (
for /f "tokens=3" %%B in ('ping -n 1 -l 1 %%A ^|findstr Reply ^|^| echo Not found Failed:') do (
set IPadd=%%B
echo %%A,!IPadd:~0, -1!>>Check.csv
))
pause
Goto Start'
here is the check.csv file contents
PB1VAL9 10.1.15.135
PB7B218 Failed
PB1VAL8 10.1.15.111
PB7B210 10.1.5.253
removed more of the same (just duplicates the above code for a different user)
Try this routine out to set your variables to ip addresses:
echo off
for /f "usebackq tokens=1,2" %%a in ("check.csv") (
echo setting %%a=%%b
set "%%a=%%b"
)
With this ping routine it shows if it is online or offline. I had to guess what you were trying to do as we don't have your files to test it.
Give us a sample of Check.csv if you want to populate a set of variables with the IP addresses inside it.
:Ping
#echo off
Del /s /q "C:\Users\dclare\Documents\VW Compiler\Copy to Desktop\Results.csv" 2>nul
Echo Pinging list...
set "ComputerList=list.txt"
pause
Echo Computername,IP Address>Final.csv
for /f "usebackq delims=" %%A in ("%ComputerList%") do (
ping -n 1 -l 1 %%A >nul
if not errorlevel 1 (
>>Check.csv echo %%A,online
) else (
>>Check.csv echo %%A,offline
)
)
pause
Goto Start

Windows Batch: Automatically upload local CSV file list via FTP

I built a script that reads a CSV file for a list of files and then uploads them to an FTP server. The CSV structure is like this:
local_file,remote_file
The script creates a text file with all the necessary FTP commands and then runs the FTP command. Everything works, except that the for loop executes code that is outside of it's command, i.e., everything below echo put "%1" "%2" >> %Commands% is also executed on every for loop, and instead of getting a nicely formatted file with all the put commands I get this (from the commands file output):
open servername
username
password
binary
put "local_path_to\first_file_on_the_list.php" "remote_path_to\first_file_on_the_list.php"
close
bye
put "-d" "-i"
close
bye
put "-d" "-i"
close
bye
put "-d" "-i"
close
...
Here is the script code:
#echo off
setlocal EnableExtensions
rem Connection credentials
set Server=servername
set UserName=username
set Password=password
set Commands="commands.txt"
echo open %Server% > %Commands%
echo %UserName% >> %Commands%
echo %Password% >> %Commands%
echo binary >> %Commands%
rem Read the CSV file line by line
for /f %%a in (matches3.csv) do call :parse %%a
rem Transform CSV line into FTP put commmand
:parse
echo put "%1" "%2" >> %Commands%
:end
rem Add commands to close ftp conn
echo close >> %Commands%
echo bye >> %Commands%
rem Perform the FTP upload
echo loggin in to ftp...
FTP -d -i -s:%Commands% %Server%
echo finished.
pause
rem Clean up.
if exist %Commands% del %Commands%
endlocal
exit
I don't understand why everything below :end is getting executed!
Thank you very much in advance
Why shouldn't the code below :end execute? You never put anything in the script to end the :parse routine.
The first ewall answer should almost work, except you need a GOTO :END after the FOR statement. I don't see how a proper implementation of his suggestion can lead to an endless loop.
Another option is to simply move your subroutine after your EXIT statement.
You have other hidden problems. File paths/names can contain spaces, so the default FOR delimiter of space,tab will not preserve the entire line if there are spaces. The default EOL will also cause any line that begins with ; to be ignored. That is a potential (but unlikely) problem, because a valid filename can begin with ;.
The solution is to set EOL to a character that cannot begin a valid file spec, and set DELIMS to nothing: "EOL=: DELIMS="
It is much more efficient to enclose all your file writing lines within parentheses and redirect the output just once. It's also easier to write and looks better.
Edit - The original script was attempting to connect to the server in both the ftp script and the ftp command line. One or the other had to be removed. I removed it from the ftp script.
#echo off
setlocal EnableExtensions
rem Connection credentials
set Server=servername
set UserName=username
set Password=password
set Commands="commands.txt"
(
echo %UserName%
echo %Password%
echo binary
rem Read the CSV file line by line
for /f "eol=: delims=" %%a in (matches3.csv) do call :parse %%a
rem Add commands to close ftp conn
echo close
echo bye
)>%Commands%
rem Perform the FTP upload
echo logging in to ftp...
FTP -d -i -s:%Commands% %Server%
echo finished.
pause
rem Clean up.
if exist %Commands% del %Commands%
endlocal
exit
rem Transform CSV line into FTP put commmand
:parse
echo put "%1" "%2"
The :end doesn't stop the CALL function from running, it's just a label. Instead, I suspect you meant to use GOTO :eof, which would return to the for-loop:
...
rem Read the CSV file line by line
for /f %%a in (matches3.csv) do call :parse %%a
rem Transform CSV line into FTP put commmand
:parse
echo put "%1" "%2" >> %Commands%
goto :eof
rem Add commands to close ftp conn
...
Or, rather than using a call, you may find it a lot simpler to use parenthesis, like this:
...
rem Read the CSV file line by line
for /f %%a in (matches3.csv) do (
rem Transform CSV line into FTP put command
echo put "%%a" "%%b" >> %Commands%
)
rem Add commands to close ftp conn
...
maybe this works
#echo off
setlocal EnableExtensions
rem Connection credentials
set Server=servername
set UserName=username
set Password=password
set Commands="commands.txt"
echo open %Server% > %Commands%
echo %UserName% >> %Commands%
echo %Password% >> %Commands%
echo binary >> %Commands%
rem Read the CSV file line by line
for /f %%a in (matches3.csv) do call :parse %%a
rem Transform CSV line into FTP put commmand
:parse
echo put "%1" "%2" >> %Commands%
:end
rem Add commands to close ftp conn
echo close >> %Commands%
echo bye >> %Commands%
rem Perform the FTP upload
echo loggin in to ftp...
FTP -d -i -s:%Commands% %Server%
echo finished.
pause
rem Clean up.
if exist %Commands% del %Commands%
endlocal
exit /b
rem Transform CSV line into FTP put commmand
:parse
echo put "%1" "%2" >> %Commands%
exit /b
:end
rem Add commands to close ftp conn
echo close >> %Commands%
echo bye >> %Commands%
exit /b