Reputation: 153
I have this script that should only run in a single instance. my method for making sure this doesn't happen is
tasklist /fi "imagename eq cmd.exe" /v | find "lootbot" && echo already running && pause && exit /b
title lootbot
and later does a graceful exit so the script can be restarted...
:action8
cd /d "%~dp0"
title command prompt
echo type launch to return to lootbot
exit /b
the problem is that whenever the script crashes because of a bug, it won't allow me to start it back up without first manually re-setting the title to something else.
not a big problem, granted, but I'm building this for other people to use so I'm looking for an easy-to-use solutions here. I'm thinking if there is a way to determine if the current command prompt is titled lootbot
then this problem could be automatically avoided. this is the whole script just in case.
@echo off
tasklist /fi "imagename eq cmd.exe" /v | find "lootbot" && echo lootbot already running, press a key to exit && pause && exit /b
title lootbot
:: enable save files location (persistant memory)
set "saves=%appdata%\lootbot"
if not exist "%saves%" mkdir "%saves%"
:: create/load saves, echo first
if exist "%saves%\echoes\launch" echo on
if not exist "%saves%\loopdelay" echo 300>"%saves%\loopdelay"
if not exist "%saves%\promptdelay" echo 60>"%saves%\promptdelay"
set /p loopdelay=<"%saves%\loopdelay" & set /p promptdelay=<"%saves%\promptdelay"
:: advanced prep
chcp 65001 >nul
set "path=%path%;%~dp0;c:\program files\filebot"
wmic process where name="cmd.exe" call setpriority "idle" >nul
:: additional scripts (all-in-one launcher)
start vpn.cmd
:: add "going legit" to vpn or somewhere (meaning close torrents and vpn, reactivate vpn if torrents appear, or close torrents)
:: add skippable delay here to auto-avoid possible mishandling ones being checked in qbittorrent
cls
:menu
@colorx -c 08
@if exist "%saves%\echoes\launch" echo on
@cd /d "%~dp0"
@set "msg=lootbot ready (%time:~0,2%:%time:~3,2%) wait %loopdelay%s or enter command"
@set "msg=%msg:( =(%" & rem this removes the blank space from early hours
@choice /t %loopdelay% /c rcplsjdq /n /d r /m "%msg% [R]un [C]lear [P]ause [L]ist [S]etup [J]obs [D]onate [Q]uit: "
:: add selective choices here, based on what scripts are available. might need those scripts to be names or placed different to autodetect
@goto action%errorlevel%
@goto menu
:action1
:routine
:: run now/default infinite loop
call report %~n0 import
call report %~n0 direct
:: idea: add small amount of random files to scan?
goto menu
:action2
:clear
cls
goto menu
:action3
pause
goto menu
:action4
:playlist
call report %~n0 player
goto menu
:action5
:settings
call report %~n0 option
goto menu
:action6
:caring
call report %~n0 caring
goto menu
:action7
:donate
call report %~n0 donate
goto menu
:action8
:exit
:: quit back to command prompt
cd /d "%~dp0"
title command prompt
echo use command launch to return to lootbot, bye!
exit /b
Upvotes: 1
Views: 1462
Reputation: 130849
This is easily done by strategically using redirection with CALL to establish a lock file. Only one instance can have write access to the lock file. Attempts by a second instance will fail. The first instance will release the lock as soon as it quits, regardless how it terminates.
I use multiple stages of redirection to hide unwanted error messages, yet the main routine has normal stdout and stderr. I add %*
to pass the original arguments to the main. The only "odd" situation in main is %0
is :main
instead of the executing script. But %~f0
can be used to get the full path to the executing script.
test.bat
@echo off
9>&2 2>nul (call :lockAndRestoreStdErr %* 8>"%~f0.lock") && (
del "%~f0.lock"
) || (
echo Only one instance allowed - "%~f0" is already running >&2
)
exit /b
:lockAndRestoreStdErr
call :main %* 2>&9
exit /b 0
:main
echo %%0 = %0
echo "%%~f0" = "%~f0"
echo Arguments = %*
pause
exit /b
Example Instance 1 output
C:\test>test arg1 arg2
%0 = :main
"%~f0" = "C:\test\test.bat"
Arguments = arg1 arg2
Press any key to continue . . .
Example Instance 2 output wile instance 1 is still running
C:\test>test arg1 arg2
Only one instance allowed - "C:\test\test.bat" is already running
C:\test>
Instance 1 will terminate once a key is pressed, and then instance 2 can run the script.
Actually you don't need a separate lock file. You can use the batch script itself as the lock file. Just remember to use >>
instead of >
, else you will wipe out the script!
@echo off
9>&2 2>nul (call :lockAndRestoreStdErr %* 8>>"%~f0") || (
echo Only one instance allowed - "%~f0" is already running >&2
)
exit /b
:lockAndRestoreStdErr
call :main %* 2>&9
exit /b 0
:main
echo %%0 = %0
echo "%%~f0" = "%~f0"
echo Arguments = %*
pause
exit /b
If users do not have write access to the script, then neither of the above will work. In that case you should put the lock file in the %temp%
folder.
@echo off
9>&2 2>nul (call :lockAndRestoreStdErr %* 8>"%temp%\%~nx0.lock") && (
del "%temp%\%~nx0.lock"
) || (
echo Only one instance allowed - "%~f0" is already running >&2
)
exit /b
:lockAndRestoreStdErr
call :main %* 2>&9
exit /b 0
:main
echo %%0 = %0
echo "%%~f0" = "%~f0"
echo Arguments = %*
pause
exit /b
To put the above technique into your script, simply remove your tasklist line, and replace it with my technique, such that the top of your script looks like:
@echo off
9>&2 2>nul (call :lockAndRestoreStdErr %* 8>>"%~f0") || (
echo Only one instance allowed - "%~f0" is already running >&2
pause
)
exit /b
:lockAndRestoreStdErr
call :main %* 2>&9
exit /b 0
:main
title lootbot
:: enable save files location (persistant memory)
etc...
Upvotes: 5