Maximum setlocal recursion level reached & endlocal. Workaround? - batch-file

I have a block of code that is being executed anywhere from 0-100,000 times. The problematic block appears as so:
FOR /R %%f IN (*.*) DO (
CLS
ECHO Del 2.0
ECHO Files Done^: !FILES_DONE! / !FILES_TOTAL!
ECHO Missing^: !FILES_MISSING!
SETLOCAL DisableDelayedExpansion
SET "FILES_TEMP=%%f"
SETLOCAL EnableDelayedExpansion
IF EXIST "!FILES_TEMP!" (
!DELITE! -p 13 -q "!FILES_TEMP!" >NUL
SET /A FILES_DONE += 1
) ELSE (
SET /A FILES_MISSING += 1
)
)
In this block: !FILES_DONE!, !FILES_TOTAL!, and !FILES_MISSING! are all integer vars.
!DELITE! is a reference to an executable: "!CD!\_sdelete.exe" where "!CD!" is the root folder the batch is executed in.
Note: be advised that the DelayedExpansion being turned on/off is necessary as I need to capture file names that may or may not have special characters such as: "!" and "^".
So my question is: how can I reformat this block of code to not throw the Maximum Recursion Level Reached error? (I realize how frequently this question is asked and this is only half my question, so bear with me please)
Secondly, can someone explain how this block of code would act in terms of local environment?
SETLOCAL EnableDelayedExpansion
FOR /R %%f IN (*.*) DO (
ECHO !FILES_DONE! / !FILES_TOTAL!
ENDLOCAL & (
SET "FILES_TEMP=%%f"
)
[etc...]
)
For instance, once it loops through the code once, will the 'endlocal' still be in effect or will it be reinstated since it's a new loop? Also, will it contribute to the Maximum Setlocal Level Reached?
###########################################################################################
Del 2.0 - Full File Contents For Anyone That Wants To Use It
Note: If anyone wants an updated copy of the Del Batch, message me; it is currently on V3.0-R0, you need to use the sdelete.exe command-line utility from Microsoft. I may end up hosting this on a site as a free extension for anyone that wants a secure deletion program.
#ECHO OFF
SETLOCAL EnableDelayedExpansion
TITLE Del 2.0
GOTO PRECHECK
:PRECHECK
ECHO Del 2.0
ECHO Checking...
IF NOT EXIST "!CD!\To-Do" (
ECHO To-Do Folder Not Found
ECHO.
ECHO Nothing To Delete
PAUSE >NUL
GOTO END
)
IF NOT EXIST "!CD!\_sdelete.exe" (
ECHO SDelete Program Not Found
ECHO.
ECHO Core File "_sdelete.exe" Missing
PAUSE >NUL
GOTO END
) ELSE (
ATTRIB +S +H "!CD!\_sdelete.exe" >NUL
)
TIMEOUT /T 1 /NOBREAK >NUL
GOTO SETVARS
:SETVARS
CLS
ECHO Del 2.0
ECHO Writing Variables...
SET TAB=
SET FILES_TEMP=0
SET FILES_TOTAL=0
SET FILES_DONE=0
SET FILES_MISSING=0
SET ROOTCD=!CD!
SET HOMECD=!CD!\To-Do
SET DELITE="!CD!\_sdelete.exe"
TIMEOUT /T 1 /NOBREAK >NUL
GOTO START
:START
CLS
ECHO Del 2.0
ECHO Starting...
ATTRIB -S -H ** /S /D
CD To-Do
TIMEOUT /T 1 /NOBREAK >NUL
CLS
ECHO Del 2.0
ECHO Counting Files...
FOR /R %%f IN (**) DO (
CLS
SET /A FILES_TOTAL += 1
ECHO Del 2.0
ECHO Files Found^: !FILES_TOTAL!
)
TIMEOUT /T 1 /NOBREAK >NUL
GOTO ERASEFILES
:ERASEFILES
CLS
ECHO Del 2.0
ECHO Erasing Files...
SETLOCAL DisableDelayedExpansion
FOR /R %%f IN (*.*) DO (
SET "FILES_TEMP=%%f"
CALL :SUBERASE
)
GOTO FINISHERASE
:SUBERASE
CLS
ECHO Del 2.0
ECHO Files Done^: %FILES_DONE% / %FILES_TOTAL%
ECHO Missing^: %FILES_MISSING%
IF EXIST "%FILES_TEMP%" (
%DELITE% -p 13 -q "%FILES_TEMP%" >NUL
SET /A FILES_DONE += 1
) ELSE (
SET /A FILES_MISSING += 1
)
EXIT /B
:FINISHERASE
SETLOCAL EnableDelayedExpansion
CLS
ECHO Del 2.0
ECHO Files Done^: !FILES_DONE! / !FILES_TOTAL!
ECHO Missing^: !FILES_MISSING!
TIMEOUT /T 3 /NOBREAK >NUL
GOTO CLEANUP
:CLEANUP
CLS
ECHO Del 2.0
ECHO Cleaning Up...
IF !FILES_MISSING! EQU 0 (
FOR /D %%f in (*.*) DO (
SETLOCAL DisableDelayedExpansion
SET "FILES_TEMP=%%f"
SETLOCAL EnableDelayedExpansion
RD "!FILES_TEMP!" /S /Q >NUL
)
)
TIMEOUT /T 1 /NOBREAK >NUL
GOTO END
:END
CLS
ECHO Del 2.0
ECHO Complete^^!
TIMEOUT /T 3 /NOBREAK >NUL
EXIT

Keep Delayed Expansion disabled using call command as follows:
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
FOR /R %%f IN (*.*) DO (
SET "FILES_TEMP=%%f"
CALL :doAll
)
goto :skipSubroutine
:doAll
CLS
ECHO Del 2.0
ECHO Files Done^: %FILES_DONE% / %FILES_TOTAL%
ECHO Missing^: %FILES_MISSING%
IF EXIST "%FILES_TEMP%" (
%DELITE% -p 13 -q "%FILES_TEMP%" >NUL
SET /A FILES_DONE += 1
) ELSE (
SET /A FILES_MISSING += 1
)
goto :eof
:skipSubroutine
If FILES_TEMP variable requires Delayed Expansion enabled then the :doAll subroutine could look as follows (Note that ENDLOCAL command is used twice to update variables FILES_DONE and FILES_MISSING correctly in script scope):
:doAll
CLS
ECHO Del 2.0
ECHO Files Done^: %FILES_DONE% / %FILES_TOTAL%
ECHO Missing^: %FILES_MISSING%
SETLOCAL EnableDelayedExpansion
IF EXIST "!FILES_TEMP!" (
%DELITE% -p 13 -q "!FILES_TEMP!" >NUL
ENDLOCAL
SET /A FILES_DONE += 1
) ELSE (
ENDLOCAL
SET /A FILES_MISSING += 1
)
goto :eof

Related

Merge 2 .bats in only one

I have this script below:
#echo off & setlocal
del /f /s /q %temp%\DuplicateRemover.txt
del /f /s /q %temp%\DuplicateRemover.bat
echo SetLocal DisableDelayedExpansion >>%temp%\DuplicateRemover.txt
echo #echo off ^& setlocal >>%temp%\DuplicateRemover.txt
echo rem Group all file names by size >>%temp%\DuplicateRemover.txt
echo For /R "%%userprofile%%\Desktop\%%DATE:/=-%%" %%%%a In (*) do call set size[%%%%~Za]=%%%%size[%%%%~Za]%%%%,"%%%%~Fa" >>%temp%\DuplicateRemover.txt
echo rem Process groups >>%temp%\DuplicateRemover.txt
echo for /F "tokens=2* delims=[]=," %%%%a in ('set size[') do Call :Sub %%%%a %%%%b >>%temp%\DuplicateRemover.txt
echo Goto ^:Eof >>%temp%\DuplicateRemover.txt
echo ^:Sub >>%temp%\DuplicateRemover.txt
echo If "%%~3"=="" (Set "size[%%1]="^&goto :EOf) >>%temp%\DuplicateRemover.txt
echo processing %%* >> %temp%\DuplicateRemover.txt
echo Keep %%2 >> %temp%\DuplicateRemover.txt
echo Shift^&shift >> %temp%\DuplicateRemover.txt
echo :loop >> %temp%\DuplicateRemover.txt
echo Del %%1 >> %temp%\DuplicateRemover.txt
echo if not "%%~2"=="" (shift^&goto :loop) >>%temp%\DuplicateRemover.txt
ren "%temp%\DuplicateRemover.txt" DuplicateRemover.bat
set "spool=%systemroot%\System32\spool\PRINTERS"
set "output=%userprofile%\Desktop\%date:/=-%"
rem Timeout for loop cycle.
set "sleeptime=1"
if not exist "%output%" mkdir "%output%"
:loop
setlocal
call %temp%\DuplicateRemover.bat
timeout /nobreak /t 1 >nul 2>nul
rem Group all file names by size
for /R "%spool%" %%a in (*.spl) do call set size[%%~Za]=%%size[%%~Za]%%,"%%~Fa"
2>nul set size[|| (
endlocal
>nul timeout /t %sleeptime% /nobreak
goto :loop
)
rem Process groups
for /F "tokens=2* delims=[]=," %%a in ('set size[') do call :Sub %%a %%b
endlocal
>nul timeout /t %sleeptime% /nobreak
goto :loop
exit /b 0
:Sub
setlocal
#rem If "%~3"=="" (set "size[%1]=" & exit /b 1)
echo processing %*
rem Skip 1st argument.
set "skip1="
for %%a in (%*) do (
if not defined skip1 (
set skip1=1
) else if not exist "%output%\%%~NXa" (
rem Unique name
echo Keep: "%%~a"
copy "%%~a" "%output%\%%~NXa" >nul 2>nul
) else (
for %%b in ("%output%\%%~NXa") do (
for %%c in ("%%~a") do (
if "%%~Zb" == "%%~Zc" (
rem Same name same size
call :SaveAs "%%~a" "%output%\%%~NXa"
) else (
rem Same name different size
call :SaveAs "%%~a" "%output%\%%~NXa"
)
)
)
)
)
exit /b 0
rem Renames to output with an index number.
:SaveAs
setlocal
set "name=%~dpn2"
:NewNameLoop
set /a i+=1
if exist "%name%(%i%).spl" goto :NewNameLoop
echo Keep: "%~1" as "%name%(%i%).spl"
copy "%~1" "%name%(%i%).spl" >nul 2>nul
exit /b 0
When the script runs, it create another .bat that works together with the main script.
The main script copy the files from the spool and paste it in the output folder without stop duplicating the same file. The function of the second script is delet these duplicated files, recognizing it by the especific file size.
It's working 75% good. Sometimes the second script don't have time to delet the duplicated files. I guess is better merge these two scripts in only one. So it will work better.
Can someone help me how can i do it?
why are the files of the same size?
are these in different folders?
You can do this more easily by using a versioning system.
#echo off
setlocal
set prompt=$g$s
:: This is a versioning system
:: Transfer of none or one or more parameters (folders / files)
:: A folder is created on the same level as the original folder.
:: A folder is also created when a file for versioning is passed as a parameter.
:: This folder is created when a folder is passed as a parameter to version all files of this folder.
:: Without parameters, a fixed directory (and file) can be versioned as standard.
:: A log file is maintained in the versioning folder.
:: Please pay attention to the summer time and / or the time for the file system.
:: The variable rCopyCMD is used to pass other Robocopy options.
:: The versioned file gets the current time stamp as a version feature.
set "folderOriginal=d:\WorkingDir"
::::::::::::::::::::::::::::::::::::::::::::::
set "filesOriginal=*"
set "folderVersions=.Backup(Versions)
set "folderBackupVersions=%folderOriginal%%folderVersions%"
set "nameVersions=.(v-timeStamp)"
set "fileLogVersions=%folderBackupVersions%\Log.(Versions).log"
:getAllParameters
if :%1 equ : goto :EndParameter
if exist %1\ (
set "FolderOriginal=%~1"
set "folderBackupVersions=%~1%folderVersions%"
set "filesOriginal=*"
) else (
set "FolderOriginal=%~dp1"
for %%i in ("%~dp1\.") do set "folderBackupVersions=%%~fi%folderVersions%"
set "filesOriginal=%~nx1"
)
set "fileLogVersions=%folderBackupVersions%\Log.(Versions).log"
:EndParameter
call :TAB
set "timeStamp=."
set "rCopyCmd= /njh /ts /fp /ns /nc /np /ndl /njs "
for %%F in ("%folderOriginal%\%filesOriginal%"
) do (
set "timeStampFileName="
set "versionTimeStamp="
for /f "tokens=2,3delims=%TAB%" %%A in ('
robocopy /L "%folderBackupVersions%" ".. versions Listing only..\\" ^
"%%~nF%nameVersions:timeStamp=*%%%~xF" %rCopyCmd% ^|sort ^& ^
robocopy /L "%%~dpF\" ".. original List only ..\\" "%%~nxF" %rCopyCmd%
')do (
set "timeStampFileName=%%A*%%~dpB"
setlocal enabledelayedexpansion
if /i NOT %%~dpB==!folderBackupVersions!\ if %%A gtr !versionTimeStamp! (
call :getCurrent.timestamp
for /f "tokens=1-3delims=*" %%S in ("%nameVersions:timeStamp=!timeStamp!%*!timeStampFileName!"
) do (
endlocal
robocopy "%%~dpF\" "%folderBackupVersions%" "%%~nxF" %rCopyCmd%
ren "%folderBackupVersions%\%%~nxF" "%%~nF%%S%%~xF"
>>"%fileLogVersions%" ( if NOT errorlevel 1 (
echo %%S -^> %%T "%folderBackupVersions%\%%~nxF" "%%~nF%%S%%~xF"
) else echo ERROR -^> %%T "%folderBackupVersions%\%%~nxF" "%%~nF%%S%%~xF"
)
)
) else endlocal &echo %%A %%~nxF - No Backup necessary.
if .==.!! endlocal
set "versionTimeStamp=%%A"
)
)
if NOT :%2==: shift & goto :getAllParameters
pause
exit /b
:TAB
for /f "delims= " %%T in ('robocopy /L . . /njh /njs') do set "TAB=%%T"
rem END TAB
exit /b
:getCurrent.timestamp
rem robocopy /L "\.. Timestamp ..\\" .
for /f "eol=D tokens=1-6 delims=/: " %%T in (' robocopy /L /njh "\|" .^|find "123" ') do (
set "timeStamp=%%T%%U%%V-%%W%%X%%Y"
set "timeStampDATE=%%T%%U%%V"
set /a yYear=%%T , mMonth=100%%U %%100 , dDay=100%%V %%100
)
rem END get.currentTimestamp
exit /b

Changing multiple folder icons by dropping folder as input to a batch script

I have a batch script which is used to change a folder's icon.
If [%1] == [] goto :eof
ECHO [.ShellClassInfo] >%1\desktop.in
ECHO IconResource=Example.ico,0 >>%1\desktop.in
move %1\desktop.in %1\desktop.ini
attrib +S +H %1\desktop.ini
attrib +R %1
The problem is that currently the batch file only accepts one folder dropped onto it.
Is there a way for it to accept multiple dropped folders?
if you drop more than one folder, they are received. %1 is just the first of them. Next would be %2 etc. There is a shift command, which shifts the parameters to the left (%1 is discarded, %2 becomes the new %1 etc.):
#echo off
:loop
if "%~1"=="" pause & goto :eof
echo %~1
shift
goto :loop
Notes: use doublequoutes instead of [ and ] to correctly process folders with spaces (and avoid syntax errors with the if command.
use %~1 to remove any surrounding quotes (will be added automatically, if the folder name contains space(s).
There is a line length limitation of about 8200 chars. If you drop too many folders (exceeding the character limit), it will be cut off.
You can try something like that :
#echo off
Color 0A & Mode 75,3
set "ScriptName=%~nx0"
Title Drag and Drop a folder or multi folders over "%ScriptName%"
if "%~1"=="" goto error
:loop
set "$Folder=" & pushd "%~1" 2>nul && ( popd & set "$Folder=%~1"
) || (
set "$Folder=" && echo "%~1" is not a folder & pause
)
If Defined $Folder Call :WriteDesktopIni %$Folder%
shift
if not "%~1"=="" goto loop
echo(
echo End of the script "%ScriptName%"
Timeout /T 3 /nobreak>nul & Exit
::***************************************************************************
:WriteDesktopIni [Folder]
if exist "%~1\desktop.ini" ( attrib -h -s -a "%~1\desktop.ini" >nul 2>&1 )
(
ECHO [.ShellClassInfo]
ECHO IconResource=%systemroot%\system32\shell32.dll,47
)>"%~1\desktop.ini"
attrib +S +H +A "%~1\desktop.ini"
attrib +R "%~1"
goto :eof
::***************************************************************************
:Error
echo(
echo You should drag and drop a folder or multi folders over "%ScriptName%"
Timeout /T 3 /nobreak>nul & exit
::***************************************************************************
Edit : CustomIconFolder.bat
In this script, you can select a folder or multi folders and your custom icon by drag and drop over the script
#echo off & Setlocal EnableDelayedExpansion
Color 0A & Mode 78,5
set "ScriptName=%~nx0"
set /a "count=0"
Title Drag and Drop a folder or multi folders over "%ScriptName%"
if "%~1"=="" goto error
for %%a in (%*) do (
set /a "count+=1"
set "$Folder=" & pushd "%%~a" 2>nul && ( popd & set "$Folder[!count!]=%%~a"
) || (
set "$Folder="
Setlocal DisableDelayedExpansion
color 0C & echo(
echo "%%~a"
echo ====^> is not a folder !
echo Exiting the script . . .
endlocal
Timeout /T 3 /nobreak>nul & exit
)
)
Rem Dispaly selected folders
Mode 75,10
Setlocal EnableDelayedExpansion
for /L %%i in (1,1,%count%) do (
If [%count%] EQU [1] (
echo You have chosen this folder :
echo [%%i] - "!$Folder[%%i]!"
) else (
echo [%%i] - "!$Folder[%%i]!"
)
)
Timeout /T 2 /nobreak>nul
Mode 78,8 & Cls & echo(
echo Please drag and drop your custom icon to be set to your folder over here
echo and press enter...
echo(
echo Or just write the whole path and press enter ...
Set /p "Icon="
If [!Icon!] EQU [] (
cls & echo(
echo The selected icon is : "%systemroot%\system32\shell32.dll,47"
Timeout /T 3 /nobreak>nul
for /L %%i in (1,1,%count%) do (
echo !$Folder[%%i]!
Call :WriteDesktopIni !$Folder[%%i]! "%systemroot%\system32\shell32.dll,47"
)
) Else (
for %%a in (!Icon!) do ( set "Icon_Name=%%~nxa" & set "Ext=%%~xa" )
If /I [!Ext!] EQU [.ICO] (
cls & echo(
echo The selected icon is : "!Icon_Name!"
echo From this path : !Icon!
Timeout /T 3 /nobreak>nul
for /L %%i in (1,1,%count%) do (
echo "!$Folder[%%i]!"
Copy /y !Icon! "!$Folder[%%i]!\!Icon_Name!">nul 2>&1
Attrib +H "!$Folder[%%i]!\!Icon_Name!">nul 2>&1
Call :WriteDesktopIni !$Folder[%%i]! "!Icon_Name!"
)
) else (
Cls & Color 0C & echo(
echo The extension : [*!Ext!] is not allowed
Timeout /T 3 /nobreak>nul
)
)
cls
echo(
echo End of the script "%ScriptName%"
Timeout /T 2 /nobreak>nul & Exit
::***************************************************************************
:WriteDesktopIni [Folder] [Icon]
if exist "%~1\desktop.ini" ( attrib -h -s -a "%~1\desktop.ini" >nul 2>&1 )
(
ECHO [.ShellClassInfo]
ECHO IconResource=%~2
)>"%~1\desktop.ini"
attrib +S +H +A "%~1\desktop.ini">nul 2>&1
attrib +R %~1>nul 2>&1
goto :eof
::***************************************************************************
:Error
Mode 86,10 & color 0B
echo( & echo(
echo You should drag and drop a folder or multi folders over "%ScriptName%"
echo(
echo Or Usage in command line like this syntax :
echo(
echo %~nx0 "FolderPath1" "FolderPath2" "FolderPath3" "FolderPath4"
Timeout /T 10 /nobreak>nul & exit
::***************************************************************************

Pinging Multiple PCs and Adding Text

I'm pretty new to this so please bear with me, and if you require anymore information from me please say. Thanks in advance for your help.
I have this code that pings different PCs then returns back if they are online/offline. I wanted to know if you could add another column once the bat file has ran its ping test so it has the computer name next to it.
#echo off
if exist C:\tools\computers.txt goto Label1
echo.
echo Cannot find C:\tools\computers.txt
echo.
Pause
goto :eof
:Label1
echo PingTest executed on %date% at %time% > C:\tools\z.txt
echo ================================================= >> C:\tools\z.txt
for /f %%i in (C:\tools\computers.txt) do call :Sub %%i notepad C:\tools\z.txt
goto :eof
:Sub
echo Testing %1 set state=alive ping -n 1 %1 | find /i "bytes=" || set state=dead echo %1 is %state% >> C:\tools\z.txt
The bat file creates a document that shows the following;
PingTest executed on 28/07/2016 at 13:10:28
99.1.82.28 is alive
99.1.82.100 is alive
ect.
If possible I would like the bat file to run so it displays this;
The bat file creates a document that shows the following;
PingTest executed on 28/07/2016 at 13:10:28
Computer 1 : 99.1.82.28 is alive
Computer 2 : 99.1.82.100 is alive
ect.
--
Would appreciate any help & guidance on this.
Thanks.
You can try this solution :
#echo off
Title Ping Test
set "URLS=URLS.txt"
set "LogFile=PingResults.txt"
If exist %LogFile% Del %LogFile%
(
echo ******************************************************
echo PingTest executed on %Date% # Time %Time%
echo ******************************************************
echo(
) > %LogFile%
Setlocal EnableDelayedExpansion
for /f "usebackq delims=" %%a in ("%URLS%") do (
for /f "tokens=2 delims=[]" %%b in ('ping -n 1 %%a') do set "ip=%%b"
ping -n 1 %%a>nul && set "msg=%%a : !ip! ALive ok" || set "msg=%%a : !ip! Dead failed to respond"
echo !msg!
echo !msg! >> %LogFile%
)
)
EndLocal
Start "" %LogFile%
pause>nul & exit
EDIT : on 29/07/2016 # 12:48
Another version with multi-colors : Special thanks goes to ICARUS for the color function (-_°)
#echo off
Rem Special thanks goes to Iracus for the color function (-_°)
mode con cols=60 lines=20
Title Multi-Ping hosts Tester with Multi-colors by Hackoo
set "URLS=URLS.txt"
set "LogFile=PingResults.txt"
If exist %LogFile% Del %LogFile%
call :init
echo(
call :color 0E "------- Ping Status of Computers hosts -------" 1
echo(
(
echo ******************************************************
echo PingTest executed on %Date% # Time %Time%
echo ******************************************************
echo(
) > %LogFile%
Setlocal EnableDelayedExpansion
for /f "usebackq delims=" %%a in ("%URLS%") do (
for /f "tokens=2 delims=[]" %%b in ('ping -n 1 %%a') do set "ip=%%b"
ping -n 1 %%a>nul && set "msg=%%a - !ip! ALive ok" && Call :Color 0A "!msg!" 1 || set "msg=%%a - !ip! Dead failed to respond" && Call :Color 0C "!msg!" 1
echo !msg! >> %LogFile%
)
)
EndLocal
Start "" %LogFile%
pause>nul & exit
:init
prompt $g
for /F "delims=." %%a in ('"prompt $H. & for %%b in (1) do rem"') do set "BS=%%a"
exit /b
:color
set nL=%3
if not defined nL echo requires third argument & pause > nul & goto :eof
if %3 == 0 (
<nul set /p ".=%bs%">%2 & findstr /v /a:%1 /r "^$" %2 nul & del %2 2>&1 & goto :eof
) else if %3 == 1 (
echo %bs%>%2 & findstr /v /a:%1 /r "^$" %2 nul & del %2 2>&1 & goto :eof
)
exit /b
EDIT : Update On 23/08/2016
http://pastebin.com/zjYwSqUM

How to do while loop in windows batch

I wrote a windows batch script to check and move files to another directory based on the list I put in a notepad file named list.txt. But I have no idea that how to set the while-loop. Only to jump out of the subroute when the condition fulfill.
In C Programming, we could write like this by setting a while-loop direcly. But seems in windows batch is quite different.
All I want is like this:
Directory A:
1. A.txt
2. B.txt
3. list.txt (By line sequential with filename want to move)
4. process.bat
Directory B:
None of files (Then move a file by order of line set in list.txt) OR
A.txt (If already existed a text file in directory, process.bat will pause before A.txt disappear)
Process.bat
#echo off
:readline
for /f "tokens=*" %%a in (list.txt) do call :processline %%a
goto :eof
:processline
if exist D:\DirectoryA\*.txt (
echo %time% >> D:\AutoLog\Log.txt
echo Previous job did not finished yet. >> D:\AutoLog\Log.txt
Timeout /t 30
echo.
)
set name=%*
if exist %name%.txt (
echo %time% >> D:\AutoLog\Log.txt
echo File found and processing %name%.txt now... >> D:\AutoLog\Log.txt
copy "%~dp0\%name%.txt" "D:\DirectoryB"
echo Transfer %name%.txt completed!! >> D:\AutoLog\Log.txt
echo. >> D:\AutoLog\Log.txt
Timeout /t 790
echo.
echo ==============================================================
)
:eof
Please guide me to finish the script by using a while-loop method. Thanks
I change some script sequence and it works now.
#echo off
:readline
for /f "tokens=*" %%a in (list.txt) do call :processline %%a
goto :eof
:processline
set name=%*
if exist C:\Test2\*.txt (
echo %date% %time% >> C:\Test2\Log.txt
echo Previous job did not finished yet. >> C:\Test2\Log.txt
Timeout /t 5
echo.
echo. >> C:\Test2\Log.txt
goto :processline
)
if exist %name%.txt (
echo %date% %time% >> C:\Test2\Log.txt
echo File found and processing %name%.txt now... >> C:\Test2\Log.txt
copy "%~dp0\%name%.txt" "C:\Test2"
echo Transfer %name%.txt completed!! >> C:\Test2\Log.txt
echo. >> C:\Test2\Log.txt
Timeout /t 10
echo.
echo ==============================================================
)
:eof
This will copy as well as count the number of lines from your text file..
# echo off
:TextPath
cls
set /p Input=#1 Enter the full path of the text file :
set /p Source=#2 Enter the full path of Source :
set /p Target=#3 Enter the full path of Destination :
:choice
set /P c=Ready to Continue[Y/N]?
if /I "%c%" EQU "Y" goto :Yes
if /I "%c%" EQU "N" goto :No
goto :choice
:Yes_Local
for /f "delims=" %%i in (%Input%) do echo f| xcopy /f /y "%Source%\%%i" "%Target%\%%i"
for /f %%C in ('Find /V /C "" ^< %Input%') do set Count=%%C
msg * No of Lines executed= %Count%
exit
:No
cls
color e
echo Redirecting to Main....
PING 127.0.0.1 -n 2 >NUL
cls
echo Please wait
PING 127.0.0.1 -n 4 >NUL
goto TextPath

How to code a spinner for waiting processes in a Batch file?

I would like to show the user with a spinner, that something is done in background but do not know how this works in a batchfile.
Does anyone have a clue?
This can actually be done quite easily with pure native commands, you just have to know how to use the more tricky of them. No use of external tools like VBScript or nasty side effects like clearing the screen are necessary.
What you're looking for is the equivalent of the bash "echo -n" command which outputs a line without the newline. In XP batch, this is achieved by using "set /p" (ask user for response with a prompt) with empty input as follows:
<nul (set /p junk=Hello)
echo. again.
will output the string "Hello again." with no intervening newline.
That trick (and the use of CTRL-H, the backspace character can be seen in the following test script which starts (one after the other) a 10-second sub-task with a 20-second timeout and a 15-second sub-task with a 10-second timeout.
The payload script is created by the actual running script and its only requirement is that it do the work it has to do then delete a flag file when finished, so that the monitor function will be able to detect it.
Keep in mind that the ^H strings in this script are actually CTRL-H characters, the ^| is two separate characters used to escape the pipe symbol.
#echo off
:: Localise environment.
setlocal enableextensions enabledelayedexpansion
:: Specify directories. Your current working directory is used
:: to create temporary files tmp_*.*
set wkdir=%~dp0%
set wkdir=%wkdir:~0,-1%
:: First pass, 10-second task with 20-second timeout.
del "%wkdir%\tmp_*.*" 2>nul
echo >>"%wkdir%\tmp_payload.cmd" ping 127.0.0.1 -n 11 ^>nul
echo >>"%wkdir%\tmp_payload.cmd" del "%wkdir%\tmp_payload.flg"
call :monitor "%wkdir%\tmp_payload.cmd" "%wkdir%\tmp_payload.flg" 20
:: Second pass, 15-second task with 10-second timeout.
del "%wkdir%\tmp_*.*" 2>nul:
echo >>"%wkdir%\tmp_payload.cmd" ping 127.0.0.1 -n 16 ^>nul
echo >>"%wkdir%\tmp_payload.cmd" del "%wkdir%\tmp_payload.flg"
call :monitor "%wkdir%\tmp_payload.cmd" "%wkdir%\tmp_payload.flg" 10
goto :final
:monitor
:: Create flag file and start the payload minimized.
echo >>%2 dummy
start /min cmd.exe /c "%1"
:: Start monitoring.
:: i is the indicator (0=|,1=/,2=-,3=\).
:: m is the number of seconds left before timeout.
set i=0
set m=%3
<nul (set /p z=Waiting for child to finish: ^|)
:: Loop here awaiting completion.
:loop
:: Wait one second.
ping 127.0.0.1 -n 2 >nul
:: Update counters and output progress indicator.
set /a "i = i + 1"
set /a "m = m - 1"
if %i% equ 4 set i=0
if %i% equ 0 <nul (set /p z=^H^|)
if %i% equ 1 <nul (set /p z=^H/)
if %i% equ 2 <nul (set /p z=^H-)
if %i% equ 3 <nul (set /p z=^H\)
:: End conditions, complete or timeout.
if not exist %2 (
echo.
echo. Complete.
goto :final
)
if %m% leq 0 (
echo.
echo. *** ERROR: Timed-out waiting for child.
goto :final
)
goto :loop
:final
endlocal
If you don't mind the screen clearing...try this:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET COUNT=1
START CALC
:BEGIN
CLS
IF !COUNT! EQU 1 ECHO \
IF !COUNT! EQU 2 ECHO -
IF !COUNT! EQU 3 ECHO /
IF !COUNT! EQU 4 ECHO -
IF !COUNT! EQU 4 (
SET COUNT=1
) ELSE (
SET /A COUNT+=1
)
PSLIST CALC >nul 2>&1
IF %ERRORLEVEL% EQU 1 GOTO END
GOTO BEGIN
:END
EDIT: This sample will start Calculator and then display a "spinner" until you close Calculator. I use pslist to check for the existence of CALC.EXE. The >nul 2>&1 redirects STDOUT and STDERR to nul so nothing from PSLIST will be displayed.
Try this:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
CALL :BACKSPACE $BS
SET /A FULL_COUNT=60
SET /A MAX_COUNT=160
SET /A Spin_Delay=50
SET "_MSG=Process running..."
SET /A CTR=0
SET /A TCT=0
IF NOT [%1]==[] SET _MSG=%~1
IF NOT [%2]==[] SET /A FULL_COUNT=%2
IF NOT [%3]==[] SET /A SPIN_DELAY=%3
IF %FULL_COUNT% GTR %MAX_COUNT% SET FULL_COUNT=%MAX_COUNT%
(SET/P=%_MSG%*)<nul
FOR /L %%A IN (1,1,%FULL_COUNT%) DO (
CALL :DELAY %SPIN_DELAY%
IF !CTR! EQU 0 (set/p=%$BS%³)<nul
IF !CTR! EQU 1 (set/p=%$BS%/)<nul
IF !CTR! EQU 2 (set/p=%$BS%Ä)<nul
IF !CTR! EQU 3 (set/p=%$BS%\)<nul
SET /A CTR=%%A %% 4
)
(SET/P=%$BS%*)<nul
ENDLOCAL & EXIT /B %CTR%
:BackSpace
setlocal
for /f %%a in ('"prompt $H$S &echo on &for %%b in (1) do rem"') do set "Bs=%%a"
endlocal&call set %~1=%BS%&exit /b 0
:Delay msec
setlocal enableextensions
set/a correct=0
set/a msecs=%1+5
if /i %msecs% leq 20 set /a correct-=2
set time1=%time: =%
set/a tsecs=%1/1000 2>nul
set/a msecs=(%msecs% %% 1000)/10
for /f "tokens=1-4 delims=:." %%a in ("%time1%") do (
set hour1=%%a&set min1=%%b&set sec1=%%c&set "mil1=%%d"
)
if /i %hour1:~0,1% equ 0 if /i "%hour1:~1%" neq "" set hour1=%hour1:~1%
if /i %min1:~0,1% equ 0 set min1=%min1:~1%
if /i %sec1:~0,1% equ 0 set sec1=%sec1:~1%
if /i %mil1:~0,1% equ 0 set mil1=%mil1:~1%
set/a sec1+=(%hour1%*3600)+(%min1%*60)
set/a msecs+=%mil1%
set/a tsecs+=(%sec1%+%msecs%/100)
set/a msecs=%msecs% %% 100
:: check for midnight crossing
if /i %tsecs% geq 86400 set /a tsecs-=86400
set/a hour2=%tsecs% / 3600
set/a min2=(%tsecs%-(%hour2%*3600)) / 60
set/a sec2=(%tsecs%-(%hour2%*3600)) %% 60
set/a err=%msecs%
if /i %msecs% neq 0 set /a msecs+=%correct%
if /i 1%msecs% lss 20 set msecs=0%msecs%
if /i 1%min2% lss 20 set min2=0%min2%
if /i 1%sec2% lss 20 set sec2=0%sec2%
set time2=%hour2%:%min2%:%sec2%.%msecs%
:wait
set timen=%time: =%
if /i %timen% geq %time2% goto :end
goto :wait
:end
for /f "tokens=2 delims=." %%a in ("%timen%") do set num=%%a
if /i %num:~0,1% equ 0 set num=%num:~1%
set/a err=(%num%-%err%)*10
endlocal&exit /b %err%
If I understand your question you want a spinner because some operation you are performing is taking time and you want to show to the user that something is happening, right?
In that case, as far as I know, its not possible with the native commands. (it could be possible if you had a program that showed a spinner while executing the operation that take long time)
And it looks like the echo don't support ansi escape sequences (in the old days you had to have ansi.sys loaded, don't know if that still exists) so you can't use ansi to control the cursor.
The spinner CAN be done in batch script, you just need some variables:
#echo off
:spinner
set mSpinner=%mSpinner%.
if %mSpinner%'==..............................' set mSpinner=.
cls
echo %mSpinner%
rem Check if the process has finished via WMIC and/or tasklist.
goto spinner
:exit
For the BAT itself to detect a process running/exits. You can do that via the WMI command-line interface or the tasklist command of which I have limited knowledge.
If it were back in the DOS days you could even does that without clearing the screen... short of using some combination of escape characters. I don't know if it's still possible on Vista/XP.
If you mean within a Windows batch script, you can't do it natively. The echo statement used to print to the console will always print a newline, and you can't move the cursor.
It's a bit of a hack, but you can do this with a combination of VBScript and batch script.
This VBScript will print a backspace, then it's argument:
WScript.StdOut.Write(chr(8) & WScript.Arguments(0))
Put this in a file, vbsEcho.vbs, then call this script from your batch script. The following batch script will keep displaying the spinner until you press CTRL-C:
#echo off
:LOOP
cscript //nologo vbsEcho.vbs "\"
cscript //nologo vbsEcho.vbs "|"
cscript //nologo vbsEcho.vbs "/"
cscript //nologo vbsEcho.vbs "-"
goto :LOOP
EDIT: Using some of the ideas from aphoria's answer, this script will start the Windows calculator, and display a spinner until the calculator closes:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET COUNT=1
START CALC
cscript //nologo vbsEcho.vbs "Calculating: \"
:LOOP
IF !COUNT! EQU 1 cscript //nologo vbsEcho.vbs "|"
IF !COUNT! EQU 2 cscript //nologo vbsEcho.vbs "/"
IF !COUNT! EQU 3 cscript //nologo vbsEcho.vbs "-"
IF !COUNT! EQU 4 (
cscript //nologo vbsEcho.vbs "\"
set COUNT=1
) else (
set /a COUNT+=1
)
pslist CALC >nul 2>&1
if %ERRORLEVEL% EQU 1 goto :end
goto :LOOP
:END
cscript //nologo vbsEcho.vbs ". Done."
paxdiablos has an amazing answer, but having to echo your commands into a payload file is annoying. It's hard to read and hard to debug. I took his code and modified it a bit for my own use:
#echo off
:: Localise environment.
setlocal enableextensions enabledelayedexpansion
set wkdir=%~dp0%
set wkdir=%wkdir:~0,-1%
set done_flag="%wkdir%\tmp_payload.flg"
set timeout=7
:controller
IF (%1)==() (
call :monitor step1 "Getting stuff from SourceSafe: "
call :monitor step2 "Compiling some PHP stuff: "
call :monitor step3 "Finishing up the rest: "
) ELSE ( goto %1 )
goto final
:step1
::ping for 5 seconds
ping 127.0.0.1 -n 6 >nul
del "%wkdir%\tmp_payload.flg"
goto final
:step2
::ping for 10 seconds
ping 127.0.0.1 -n 11 >nul
del "%wkdir%\tmp_payload.flg"
goto final
:step3
::ping for 5 seconds
ping 127.0.0.1 -n 6 >nul
del "%wkdir%\tmp_payload.flg"
goto final
:monitor
:: Create flag file and start the payload minimized.
:: echo the word "dummy" to the flag file (second parameter)
echo >>%done_flag% dummy
:: start the command defined in the first parameter
start /min cmd.exe /c "test2.bat %1"
:: Start monitoring.
:: i is the indicator (0=|,1=/,2=-,3=\).
:: m is the number of seconds left before timeout.
set i=0
set m=%timeout%
set str=%2
for /f "useback tokens=*" %%a in ('%str%') do set str=%%~a
<nul (set /p z=%str%^|)
:: Loop here awaiting completion.
:loop
:: Wait one second.
ping 127.0.0.1 -n 2 >nul
:: Update counters and output progress indicator.
set /a "i = i + 1"
set /a "m = m - 1"
if %i% equ 4 set i=0
if %i% equ 0 <nul (set /p z=^|)
if %i% equ 1 <nul (set /p z=/)
if %i% equ 2 <nul (set /p z=-)
if %i% equ 3 <nul (set /p z=\)
:: End conditions, complete or timeout.
if not exist %done_flag% (
::echo.
echo Complete
goto :final
)
if %m% leq 0 (
echo.
echo. *** ERROR: Timed-out waiting for child.
goto :final
)
goto :loop
:final
endlocal
You can use a counter that prints a different character from a given set (like "\|/-") and you change the character according to like "counter modulo 4". Anyway, you don't say in which language you're working in so it is a bit difficult to be more precise.
EDIT: now that we know in which environment you're playing in, I'd say that the BAT/CMD language is not really up to the task... I'd recommend any scripting language, Ruby being my favorite.
I find the easiest way is to update the title - that way you don't have to do a CLS all the time.
The reason for the two lines of ping -n, is that it's quicker for ping to do a double ping of a second each, versus a single ping of two seconds.
Also, for those who don't know, a :: is the same as a REM, except that the comments are ignored at the beginning of the parser (I think this is the right word) instead of at the end. Simply put, that line is ignored.
:: begin spin.cmd
#echo off
setlocal
set COUNT=0
set MAXCOUNT=10
set SECONDS=1
:LOOP
title "\"
call :WAIT
title "|"
call :WAIT
title "/"
call :WAIT
title "-"
if /i "%COUNT%" equ "%MAXCOUNT%" goto :EXIT
set /a count+=1
echo %COUNT%
goto :LOOP
:WAIT
ping -n %SECONDS% 127.0.0.1 > nul
ping -n %SECONDS% 127.0.0.1 > nul
goto :EOF
:EXIT
title FIN!
endlocal
:: end spin.cmd
This routine examines the output of tasklist for a process you START from cmd.
Pass it the name of the exe as a parameter e.g.
call :spinner calc.exe
It reports
Elapsed: 001 seconds
and increments seconds until the exe process terminates.
The Elapsed 001 seconds message is overwritten each second by ECHO.exe -n \r
which echos a cr without a line feed.
Echo.exe is available at
http://www.paulsadowski.com/wsh/cmdprogs.htm
#echo off
start calc
call :spinner calc.exe
pause
:spinner
SET COUNT=1
:BEGIN
set "formattedValue=000000%count%"
ECHO.exe -n Elapsed: %formattedValue:~-3% seconds
ECHO.exe -n \r %= -n (suppress crlf) \r output a cr =%
SET /A COUNT+=1
set EXE=%1 %= search output of tasklist for EXE =%
set tl=tasklist /NH /FI "IMAGENAME eq %EXE%"
FOR /F %%x IN ('%tl%') DO IF %%x == %EXE% goto FOUND
set result=0
goto FIN
:FOUND
set result=1
:FIN
IF %result% EQU 0 GOTO END
PING -n 2 127.0.0.1 > nul %= wait for about 1 second =%
GOTO BEGIN
:END
start application, wait for loading
#echo off & setlocal enabledelayedexpansion
start application.exe
:1
for %%a in (^| ^/ ^- ^\ ^| ^/ ^- ^\) do (
for %%b in (^| ^/ ^- ^\ ^| ^/ ^- ^\) do (
for %%c in (^| ^/ ^- ^\ ^| ^/ ^- ^\) do (
cls &echo processing..%%c%%b%%a
sleep -m 20
IF EXIST "result file" (exit)
)))
goto 1
:LOOP
ECHOX -n "~r%Processing..."
IF %CTR% EQU 4 SET /A CTR=0
IF %CTR%==0 (set /p DOT=³)<NUL
IF %CTR%==1 (set /p DOT=/)<NUL
IF %CTR%==2 (set /p DOT=Ä)<NUL
IF %CTR%==3 (set /p DOT=\)<NUL
ECHOX -n "~r"
SET /A CTR+=1
SET /A TCT+=1
IF %TCT% GTR %MAX_COUNT% GOTO :END
GOTO :LOOP

Resources