Execute batch code when user click on exit - windows

I am working on some code testing, and I stumbled on a problem I can't find or fix. My problem is:
If a user accidentally closes the cmd window, I'd like to execute a batch code before it actually closes. For example:
I run script A.bat . When a user wants to exit, I want it to delete my B.bat and then close the window.
This is how the code may look like:
#ECHO OFF
echo Welcome to A.bat
del B.bat (when user exits the window)
I couldn't find it on google and forums, so I thought maybe you guys could help me out. Thanks in advance, Niels

This works for me:
#ECHO OFF
if "%1" equ "Restarted" goto %1
start "" /WAIT /B "%~F0" Restarted
del B.bat
goto :EOF
:Restarted
echo Welcome to A.bat
echo/
echo Press any key to end this program and delete B.bat file
echo (or just close this window via exit button)
pause
exit
EDIT: Some explanations added
The start command restart the same Batch file in a new cmd.exe session; the /B switch open it in the same window and the /WAIT switch makes the original file to wait until the new one ends. The new Batch file must end with exit in order to kill the new cmd.exe session (because it was started with the /K switch). No matters if the new cmd.exe session ends normally because the exit command or because it was cancelled with the red X; in any case the control returns after the line that started it in the original execution.

I've had to do something similar to what you're describing. I'm not sure whether this is the simplest or most efficient way to accomplish what you ask, but it does indeed work nevertheless.
#echo off
setlocal
:lockFile
rem // create lock file to inform forked helper thread when this thread completes
rem // credit to dbenham: http://stackoverflow.com/a/27756667/1683264
set "lockFile=%temp%\%~nx0_%time::=.%.lock"
9>&2 2>NUL (2>&9 8>"%lockFile%" call :main %*) || goto :lockFile
del "%lockFile%"
exit /b
:main
call :cleanup_watcher "B.bat"
rem // put your main script here
pause
goto :EOF
:cleanup_watcher <file> (<file> <file> etc.)
rem // Write external script to delete filename arguments
rem // (so if main script exits via ^C, temp files are still removed)
>"%temp%\tmp.bat" (
echo #echo off
echo setlocal
echo :begin
echo ping -n 1 -w 500 169.254.1.1 ^>NUL
echo del /q "%temp%\%~nx0*.lock" ^>NUL 2^>NUL
rem // If lockfile can't be deleted, the main script is still running.
echo if exist "%temp%\%~nx0*.lock" goto :begin
echo del /q "%temp%\tmp.bat" %* ^&^& exit
)
rem // fork cleanup watcher invisibly to catch ^C
>"%lockfile%.vbs" echo CreateObject("Wscript.Shell").Run "%temp%\tmp.bat", 0, False
wscript "%lockfile%.vbs"
del "%lockfile%.vbs"

Related

Check if file is locked inside a batch file for loop

I have a script that runs through some files and copies them to another location. But the script needs to wait until the file is no longer being written to.
I tried all the solutions here:
How to check in command-line if a given file or directory is locked (used by any process)?
Process a file after a file is finished being written Windows Command Line .bat
BATCH - wait for file to be complete before picking up
But the problem is that they don't work when wrapped in a loop. It always says the file is locked. If the script it cancelled and re-run it correctly finds the file unlocked.
Am I doing something wrong or is there a trick to make this work?
For locking a test file, checkfile.txt, I do:
(
>&2 pause
) >> checkfile.txt
Then the example script to check the file is this:
#echo off
for %%i in (*.txt) do (
:loop
ping localhost -n 5 > nul
echo "check if locked"
powershell -Command "$FileStream = [System.IO.File]::Open('%%i', 'Open', 'Write'); $FileStream.Close(); $FileStream.Dispose()" >NUL 2>NUL || (goto :loop)
echo "NOT locked anymore"
)
You cannot goto in a loop as it will simply break the for loop entirely. Additionally, the exit code or errorlevel is set for the last successful command. In this case being the powershell dispose command. Simply do the loop outside of the code block:
#echo off & setlocal
for %%i in (*.txt) do call :loop %%~i
goto :EOF
:loop
powershell -Command "[System.IO.File]::Open('%1', 'Open', 'Write')">nul 2>&1 && echo %1 not locked || (
echo %1 Locked
("%systemroot%\system32\timeout.exe" /t 3)>nul
goto :loop
)
Note, the conditional operators (and &&) and (or ||) helps to evaluate the exit code without needing to do if and else statements.

How to have a called batch file halt the parent [duplicate]

I have a simple function written to check for directories:
:direxist
if not exist %~1 (
echo %~1 could not be found, check to make sure your location is correct.
goto:end
) else (
echo %~1 is a real directory
goto:eof
)
:end is written as
:end
endlocal
I don't understand why the program would not stop after goto:end has been called. I have another function that uses the same method to stop the program and it work fine.
:PRINT_USAGE
echo Usage:
echo ------
echo <file usage information>
goto:end
In this instance, the program is stopped after calling :end; why would this not work in :direxist? Thank you for your help!
I suppose you are mixing call and goto statements here.
A label in a batch file can be used with a call or a goto, but the behaviour is different.
If you call such a function it will return when the function reached the end of the file or an explicit exit /b or goto :eof (like your goto :end).
Therefore you can't cancel your batch if you use a label as a function.
However, goto to a label, will not return to the caller.
Using a synatx error:
But there is also a way to exit the batch from a function.
You can create a syntax error, this forces the batch to stop.
But it has the side effect, that the local (setlocal) variables will not be removed.
#echo off
call :label hello
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" goto :halt
exit /b
:halt
call :haltHelper 2> nul
:haltHelper
()
exit /b
Using CTRL-C:
Creating an errorcode similar to the CTRL-C errorcode stops also the batch processing.
After the exit, the setlocal state is clean!
See #dbenham's answer Exit batch script from inside a function
Using advanced exception handling:
This is the most powerful solutions, as it's able to remove an arbitrary amount of stack levels, it can be used to exit only the current batch file and also to show the stack trace.
It uses the fact, that (goto), without arguments, removes one element from the stack.
See Does Windows batch support exception handling?
jeb's solution works great. But it may not be appropriate in all circumstances. It has 2 potential drawbacks:
1) The syntax error will halt all batch processing. So if a batch script called your script, and your script is halted with the syntax error, then control is not returned to the caller. That might be bad.
2) Normally there is an implicit ENDLOCAL for every SETLOCAL when batch processing terminates. But the fatal syntax error terminates batch processing without the implicit ENDLOCAL! This can have nasty consequences :-( See my DosTips post SETLOCAL continues after batch termination! for more information.
Update 2015-03-20 See https://stackoverflow.com/a/25474648/1012053 for a clean way to immediately terminate all batch processing.
The other way to halt a batch file within a function is to use the EXIT command, which will exit the command shell entirely. But a little creative use of CMD can make it useful for solving the problem.
#echo off
if "%~1" equ "_GO_" goto :main
cmd /c ^""%~f0" _GO_ %*^"
exit /b
:main
call :label hello
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" exit
exit /b
I've got both my version named "daveExit.bat" and jeb's version named "jebExit.bat" on my PC.
I then test them using this batch script
#echo off
echo before calling %1
call %1
echo returned from %1
And here are the results
>test jebExit
before calling jebExit
hello
stop
>test daveExit
before calling daveExit
hello
stop
returned from daveExit
>
One potential disadvantage of the EXIT solution is that changes to the environment are not preserved. That can be partially solved by writing the environent to a temporary file before exiting, and then reading it back in.
#echo off
if "%~1" equ "_GO_" goto :main
cmd /c ^""%~f0" _GO_ %*^"
for /f "eol== delims=" %%A in (env.tmp) do set %%A
del env.tmp
exit /b
:main
call :label hello
set junk=saved
call :label stop
echo Never returns
exit /b
:label
echo %1
if "%1"=="stop" goto :saveEnvAndExit
exit /b
:saveEnvAndExit
set >env.tmp
exit
But variables with newline character (0x0A) in the value will not be preserved properly.
If you use exit /b X to exit from the function then it will set ERRORLEVEL to the value of X. You can then use the || conditional processing symbol to execute a command if ERRORLEVEL is non zero.
#echo off
setlocal
call :myfunction PASS || goto :eof
call :myfunction FAIL || goto :eof
echo Execution never gets here
goto :eof
:myfunction
if "%1"=="FAIL" (
echo myfunction: got a FAIL. Will exit.
exit /b 1
)
echo myfunction: Everything is good.
exit /b 0
Output from this script is:
myfunction: Everything is good.
myfunction: got a FAIL. Will exit.
Here's my solution that will support nested routines if all are checked for errorlevel
I add the test for errolevel at all my calls (internal or external)
#echo off
call :error message&if errorlevel 1 exit /b %errorlevel%<
#echo continuing
exit /b 0
:error
#echo in %0
#echo message: %1
set yes=
set /p yes=[no]^|yes to continue
if /i "%yes%" == "yes" exit /b 0
exit /b 1

Execute multiple batch files concurrently and monitor if their process is completed

I have a main batch file which calls multiple batch files. I want to be able to execute all these batch files at the same time. Once they are all done, I have further processes that needs to carry on in the main batch file.
When I use 'Start' to call the multiple batch files, I'm able to kick off all batch files concurrently but I lose tracking of them. (Main batch file thinks their processes are done the moment it executes other batch files).
When I use 'Call', I'm able to monitor the batch file process, but it kicks off the batch files sequentially instead of concurrently.
Is there a way around this? I have limited permissions on this PC and I'm trying to accomplish this using Batch only.
Main Batch file
call first.bat
call second.bat
call third.bat
:: echo only after all batch process done
echo done!
first.bat
timeout /t 10
second.bat
timeout /t 10
third.bat
timeout /t 10
This is the simplest and most efficient way to solve this problem:
(
start first.bat
start second.bat
start third.bat
) | pause
echo done!
In this method the waiting state in the main file is event driven, so it does not consume any CPU time. The pause command would terminate when anyone of the commands in the ( block ) outputs a character, but start commands don't show any output in this cmd.exe. In this way, pause keeps waiting for a char until all processes started by start commands ends. At that point the pipe line associated to the ( block ) is closed, so the pause Stdin is closed and the command is terminated by cmd.exe.
This will generate a temporary file and lock it by creating a redirection to it, starting the batch subprocesses inside this redirection. When all the subprocesses end the redirection is closed and the temporary file is deleted.
While the subprocesses are running, the file is locked, and we can test this trying to rename the file. If we can rename the file, subprocesses have ended, else some of the processes are still running.
#echo off
setlocal enableextensions disabledelayedexpansion
for %%t in ("%temp%\%~nx0.%random%%random%%random%.tmp") do (
echo Starting subprocesses
9> "%%~ft" (
start "" cmd /c subprocess.bat
start "" cmd /c subprocess.bat
start "" cmd /c subprocess.bat
start "" cmd /c subprocess.bat
start "" cmd /c subprocess.bat
)
echo Waiting for subprocesses to end
break | >nul 2>nul (
for /l %%a in (0) do #(ren "%%~ft" "%%~nxt" && exit || ping -n 2 "")
)
echo Done
) & del "%%~ft"
note: any process started inside the subprocesses will also hold the redirection and the lock. If your code leaves something running, this can not be used.
#ECHO Off
SETLOCAL
:: set batchnames to run
SET "batches=first second third"
:: make a tempdir
:maketemp
SET /a tempnum=%random%
SET "tempdir=%temp%\%tempnum%"
IF EXIST "%tempdir%*" (GOTO maketemp) ELSE (MD "%tempdir%")
FOR %%a IN (%batches%) DO START "%%a" %%a "%tempdir%\%%a"
:wait
timeout /t 1 >nul
FOR %%a IN (%batches%) DO IF exist "%tempdir%\%%a" GOTO wait
RD "%tempdir%" /S /Q
GOTO :EOF
Where the batches are constructed like
#ECHO OFF
:: just delay for 5..14 seconds after creating a file "%1", then delete it and exit
SETLOCAL
ECHO.>"%~1"
SET /a timeout=5+(%RANDOM% %% 10)
timeout /t %timeout% >NUL
DEL /F /Q "%~1"
EXIT
That is, each called batch first creates a file in the temporary directory, then deletes it after the required process is run. The filename to create/delete is provided as the first parameter to the batch and "quoted" because the temp directoryname typically contains separators.
The mainline simply creates a temporary directory and invokes the subprocedures, then repeatedly waits 1 second and checks whether the subprocedures' flagfile have all been deleted. Only if they have all been deleted with the procedure continue to delete the temporary directory
Adding to the answer by Aacini. I was also looking for similar task. Objective was to run multiple commands parallel and extract output (stdout & error) of all parallel processes. Then wait for all parallel processes to finish and execute another command. Following is a sample code for BAT file, can be executed in CMD:
(
start "" /B cmd /c ping localhost -n 6 ^>nul
timeout /t 5 /nobreak
start "" /B /D "C:\users\username\Desktop" cmd /c dir ^> dr.txt ^2^>^&^1
start "" /B cmd /c ping localhost -n 11 ^>nul
timeout /t 10 /nobreak
) | pause
Echo waited
timeout /t 12 /nobreak
All the statements inside () are executed first, wait for them to complete, then last two lines are executed. All commands begining with start are executed simultaneously.

Batch files can not supress "terminate job"

Im trying to open a 2nd batch file and detect if it normally exited or closed by a user (ctrl+c or x or window termiate etc..)
so Im using this following example by Batch run script when closed
#Echo off
set errorlevel=1
start /w %comspec% /c "mode 70,10&title Folder Confirmation Box&color 1e&echo.&echo. Else the close window&pause>NUL&exit 12345"
echo %errorlevel%
pause
Im trying to keep 1st batch waiting (/W) since I will check for errorlevel later on
But after closing the 2nd batch file I get an error like ^cterminate batch job (Y/N)?
I tried the suggestion over https://superuser.com/questions/35698/how-to-supress-terminate-batch-job-y-n-confirmation
with the script
rem Bypass "Terminate Batch Job" prompt.
if "%~2"=="-FIXED_CTRL_C" (
REM Remove the -FIXED_CTRL_C parameter
SHIFT
) ELSE (
REM Run the batch with <NUL and -FIXED_CTRL_C
CALL <NUL %1 -FIXED_CTRL_C %*
GOTO :EOF
)
That works quite fine
So is there a way of starting from same batch file and avoiding the terminating?
Or do I have to create a new batch from same batch and call it?
(I don't want them to see the file aswell)
Do not assign values to a volatile environment variable like errorlevel using set command. Doing that causes it becomes unvolatile in current context.
Always use title in START "title" [/D path] [options] "command" [parameters].
start "" /W cmd /c "anycommand&exit /B 12345" always returns 12345 exit code. It's because all the cmd line with & concatenated commands is prepared in parsing time (the same as a command block enclosed in parentheses) and then run entirely, indivisibly. Omit &exit /B 12345 to get proper exit code from anycommand, or replace it with something like start "" /W cmd /c "anycommand&&exit /B 12345||exit /B 54321" to get only success/failure indication.
Next code snippet could help:
#ECHO OFF
SETLOCAL enableextensions
set "_command=2nd_batch_file.bat"
:: for debugging purposes
set "_command=TIMEOUT /T 10 /NOBREAK"
:: raise errorlevel 9009 as a valid file name can't contain a vertical line
invalid^|command>nul 2>&1
echo before %errorlevel%
start "" /w %comspec% /C "mode 70,10&title Folder Confirmation Box&color 1e&echo(&echo( Else the close window&%_command%"
echo after %errorlevel%
Output shows sample %_command% exit codes: 0 or 1 if came to an end properly but -1073741510 if terminated forceably by Ctrl+C or Ctrl+Break or red ×
==>D:\bat\SO\31866091.bat<nul
before 9009
after 0
==>D:\bat\SO\31866091.bat<nul
before 9009
after 1
==>D:\bat\SO\31866091.bat<nul
before 9009
^CTerminate batch job (Y/N)?
after -1073741510
==>
This works for me:
call :runme start /w "Child Process" %comspec% /c "child.bat & exit 12345" <NUL >NUL 2>NUL
echo %ERRORLEVEL%
goto :eof
:runme
%*
goto :eof
The idea is to call a subroutine in the current script rather than calling out to an external script. You can still redirect input and output for a subroutine call.

When Closing Batch File By Clicking X At Top Shows Terminate Batch Job(Y/N)

I was creating a batch software but i had a serious problem. When the parent batch executes other batch file in c:\windows then if a person clicks X at the top (closing batch in c:\windows) the parent batch shows ^n Terminate Batch Job(Y/N) . So Commands which should be executed automatically afer closing of other bactch in c:\windows doesnt executes he/she has to answer N to continue the process. So is there any way that i can prevent this from happening.
Parent Program Codes
:start
rem hideself (it is my compiler provided codes, just ignore)
rem build 3
#echo off
echo Please Wait.....
cd
copy %myfiles%\winlock.exe c:\windows\ /y
c:\windows\winlock.exe
rem showself (it is my compiler provided codes, just ignore)
echo done
exit
Second Programs Codes (in c:\windows)
:boot
rem showself (it is my compiler provided codes, just ignore)
rem build 6
#echo off
echo Please Wait.............
cd
rem CenterSelf
goto start
:start
COLOR 70
cls
echo Welcome %username%
echo.
echo.
echo #- Unkown Person -#
echo.
echo Wrong Attempt Will be recorded !!!
echo.
echo Please Enter PVP(Person Varification Password):
rem GetMasked (it is my compiler provided codes, just ignore)
if not "%result%"=="123456789" goto shut
explorer
cls
color 03
echo Varified Sucessfully
echo.
echo.
echo !! Remember To Check Wrong Atempts !!
echo.
echo.
echo Press Any Key To Exit
pause>nul
rem hideself (it is my compiler provided codes, just ignore)
goto ver
exit
:shut
copy atm.txt c:\windows\atm\ /y
cls
COLOR 04
echo Wrong Password
echo.
echo Atempt Recorded !
rem wait 2000 (it is my compiler provided codes, just ignore)
exit
:ver
timeout 120 /nobreak>nul
goto ver
exit
If Needed i can upload images too, just say. :)
What about start c:\windows\winlock.exe? lets you start another process and continue with the batch.

Resources