batch - is there a way to synchronize-lock txt file in batch? - file

I need to create a batch file that trace out using tracert command some ip, and write the trace to txt files. I want it to be fast so I want to start for each trace a new command to make all the trace request start at once.
there is my ping.bat:
#echo off
set saveUnrechableLocation=..\pingUnreachableInfo\pingUnrechableInfoDB.txt
set IpListLocation=..\ipInfo\all_DB_ip.txt
set reachableLocation=..\pingRechableInfo\RechableIp\pingRechableInfoDB.txt
set trace=..\pingRechableInfo\tracert\tracertDB.txt
set numberOfPings=1
#echo pinging DB > %saveUnrechableLocation%
copy /y NUL %reachableLocation% > NUL
copy /y NUL %trace% > NUL
for /F "tokens=*" %%A in (%IpListLocation%) do (
ping -n %numberOfPings% %%A | find "TTL=" >nul
if errorlevel %numberOfPings% (
#echo %%A not rechable >> %saveUnrechableLocation%
)
if not errorlevel %numberOfPings% (
#echo %%A >> %reachableLocation%
start trace.bat %trace% %%A
)
)
and the trace.bat look like that:
#echo off
set saveLocation=%~1
set ip=%~2
tracert %ip% >> %saveLocation%
exit
the problem is that when I'm trying to use this I'm getting this problem:
the process cannot access the file because it is being used by another process
what can I do to resolve this problem? thanks!

Windows redirection does not allow multiple processes to have the same file open for write access at the same time. The write operations must be serialized. This can be done with batch processing, as demonstrated at https://stackoverflow.com/a/9344547/1012053. However, I don't think that solution will help in your case.
Each tracert process takes considerable time, and the output must be redirected the entire time. But you want multiple processes running at the same time, all output redirected to the same file. Even if you were to make it work, the output would be interleaved, and you wouldn't be able to figure out what it all means.
I recommend redirecting each tracert output to a unique file. You could incorporate the ip address into the output file name, you could use the technique I showed to merge the files after each process completes.
Note there is no need to pass the output location. Each child process has access to the trace variable, so it can easily redirect to the correct location.
outline of ping.bat changes
...
set trace=..\pingRechableInfo\tracert\tracertDB
...
start trace.bat %%A
...
modified trace.bat
#echo off
tracert %1 >%trace%_%1.txt %= Redirect TRACERT to unique temp file =%
:merge
2>nul ( %= Hide error messages inside the outer parentheses =%
>>%trace%.txt ( %= Attempt stdout redirection - Fails if already locked =%
type %trace%_%1.txt %= Write the temp file to the merge file =%
(call ) %= Clear any error that TYPE may have generated =%
)
) || goto :merge %= Loop back and try again if stdout redirection failed =%
del %trace%_%1.txt %= Delete the temporary file =%
exit
A shortened form without comments could look like:
#echo off
tracert %1 >%trace%_%1.txt
:merge
2>nul (>>%trace%.txt (type %trace%_%1.txt&(call )))||goto :merge
del %trace%_%1.txt
exit

this is fixed code based on dbenham answer:
#echo off
tracert %1 >%trace%_%1.txt
:merge
2>nul (
>>%trace%.txt (
type %trace%_%1.txt
(call )
)
) ||goto :merge
del %trace%_%1.txt
exit

Related

Batch file to edit .ini file but do not delete blank space

I am new to StackOverflow. I want to run a batch file to find and replace a single string in an .ini file. I tried several solutions given on stackoverflow and other sites too.
A few of them are working - but delete my other lines having "space" or ";".
Here is the string that I want to find and change in my file RDConfigSettings.ini
CommunicationMode:1
I want it vice-versa:
if it is "CommunicationMode:1" then change it to "CommunicationMode:0"
if it is "CommunicationMode:0" then change it to "CommunicationMode:1"
Here is the whole content of my RDConfigSettings.ini file
;0 for Staging, 1 for Pre-Production, 2 for Production
RDEnviroment:2
;0 for disable, 1 for Enable
RDServiceLogs:0
;0 for disable, 1 for Enable
ClientValidation:0
;Validate Management Server Certificate -- 0 for Yes, 1 for No
ValidateCertificate:0
;Proxy Configuration -- 0 for Direct Internet Access, 1 for Access via Proxy
ProxyConfig:0
ProxyIP:[Proxy IP]
ProxyPort:[Proxy Port]
;0 for Https, 1 for Http
CommunicationMode:1
;Port Range Setting in below field
PortBegin:11100
PortEnd:11120
;ManagementServerURL
Registration:https://rdm.smartbioplus.com/rdm-device-app/registration
Keyrotation:https://rdm.smartbioplus.com/rdm-key-management-app/keyRotation
Telemetry:https://rdm.smartbioplus.com/rdm-telemetry-app/telemetry
Domain:rdm.smartbioplus.com
URL_Port:443
Could anyone help me? THis is my code:
#echo off
set "file=E:\CSC Softwares\MorphoRdServiceL0Soft\RDConfigSettings.ini"
:loop
findstr "^CommunicationMode:0$" "%file%" >nul || (
type "%file%"|repl "^CommunicationMode:1" "CommunicationMode:0" >"%file%.tmp"
move "%file%.tmp" "%file%" >nul
)
timeout 120 >nul
goto :loop
Moreover, it will be a great help if someone can add an Command with administrative rights that will stop a particular service "MORPHO_RD_Service" before replacing the string and then after replace the string, start the same service again.
You have code to switch from 1 to 0, but no code to switch from 0 to 1.
Below code alternates between 1 and 0 with each run of the loop.
I also changed to jrepl (more modern and powerful). It isn't necessary (though possible) to process piped data and redirect the result to another file. The /f switch gives the inputfile to process, the /o switch gives the outputfile. By giving it a single -, it uses the same filename as the input file (and overwrites it with the new(changed) data).
#echo off
set "file=t.txt"
:loop
findstr "^CommunicationMode:" "%file%" & REM this line for troubleshooting only
findstr "^CommunicationMode:0$" "%file%" >nul && (
call jrepl "CommunicationMode:0" "CommunicationMode:1" /f "%file%" /o -
) || (
call jrepl "CommunicationMode:1" "CommunicationMode:0" /f "%file%" /o -
)
timeout 1 >nul
goto :loop
Don't forget to adapt the data file name and the timeout to your needs.
Without the need for an external utility such as jrepl, which is great for some things, but not needed for such a task:
#echo off
setlocal enabledelayedexpansion
set "file=E:\CSC Softwares\MorphoRdServiceL0Soft\RDConfigSettings.ini"
for /f "tokens=1,*delims=]" %%i in ('type "%file%" ^| find /v /n "" ^& break^>"%file%"') do (
set "line=%%j"
if "!line!" == "CommunicationMode:1" (
set "line=!line:1=0!"
set "hold=!line!"
) else if "!line!" == "CommunicationMode:0" (
set "line=!line:0=1!"
set "hold=!line!"
)
echo(!line!>>"!file!"
)
echo Changed to !hold!
pause

Loop works for first file then fails

I am a complete novice when it comes to scripting, but am attempting to write a batch script which runs a command to output a png file to a printer. The script I have works fine for one file, but when there are multiple files it does not.
Can anyone point me in the right direction please?
#echo off
REM ___Change Directory to where Label Is Stored___
pushd C:\AFP\to
REM ___Create Variable to capture filename of any png file___
for /F %%a in ('dir /b *.png') do set FileName=%%~na.png
REM ___Now we have the filename as a variable, send it to printer using Zebra SSDAL___
\\172.16.100.2\nDrive\Prime_DPD_Label_Print\ssdal.exe /p "TSC DA200" send %FileName% >> C:\AFP\Log\Label_Printing_Log.txt
REM ___Copy PNG File to Backup Folder___
XCOPY /y /q /c C:\AFP\to\*.png C:\AFP\backup\
REM ___Delete PNF File from To Folder___
DEL C:\AFP\to\*.png
When the script runs, the first file prints fine. The subsequent files then do not print, I get "File does not exist" back from the ssdal.exe command. Why would the first one work but not the subsequent prints? I would have expected the for to loop through.
#ECHO Off
SETLOCAL
REM ___Change Directory to where Label Is Stored___
pushd C:\AFP\to
REM ___Process all png files___
for /F "delims=" %%a in ('dir /b *.png') do (
REM ___Now we have the filename as "%%a", send it to printer using Zebra SSDAL___
\\172.16.100.2\nDrive\Prime_DPD_Label_Print\ssdal.exe /p "TSC DA200" send "%%a" >> C:\AFP\Log\Label_Printing_Log.txt
IF ERRORLEVEL 1 (
CALL ECHO SSDAL returned ERRORLEVEL %%errorlevel%% FOR "%%a"
) ELSE (
REM ___Move PNG File to Backup Folder___
IF EXIST "c:\afp\backup\%%a" (
ECHO MOVE "%%a" to backup skipped as file already exists IN backup
) ELSE (
MOVE "%%a" C:\AFP\backup\
)
)
REM Two-second delay
TIMEOUT /t 2 >nul 2>nul
)
POPD
GOTO :EOF
Ah! using Zebra printers. Sensible lad!
This replacement script should do what you want.
The setlocal command is used to ensure that any variation made by this batch to the cmd environment is discarded when the batch ends.
The delims= sets "no delimiters" so for/f will set %%a to the entire filename, even if it contains spaces or other delimiters. Quoting %%a ensures such filenames are kept together as a single unit, not interpreted as separate tokens.
I'm assuming that ssdal acts responsibly and returns errorlevel non-zero in the case of errors. The if errorlevel 1 means if the errorlevel is currently 1 or greater than 1 and in that case, the error message is generated. We need to call echo ... %%varname%% ... in order to display the current value of the variable, if we're not using delayed expansion (many SO articles explain this)
Otherwise, if ssdal was successful, check for the existence of the filename in the backup directory, and either move it there or report that it already exists.
Of course, there are many ways in which this could be manipulated if features I've added are not desired. I'm happy to adjust this script to comply.
timeout is a standard utility to wait for a keypress. The redirection takes care of its prompting (it will provide a countdown unless gagged).

execute some code for each element of a list in batch

set machines=PC2 TEST-PC PC34
for %%a in (%machines%) do (
echo %%a
echo.
) > PCs.txt
I want to loop through a list of machines and execute code for each machine.
But in my PCs.txt there is only one line with PC34.
What's my mistake?
Thanks in advance!
The loop is working properly; the only problem is that redirection to a file overwrites the file if it already exists. To append instead, use the >> redirection operator. (Clear the file beforehand to get rid of any existing content.) Like so:
set machines=PC2 TEST-PC PC34
copy /Y NUL: PCs.txt
for %%a in (%machines%) do (
echo %%a
echo.
) >> PCs.txt
The problem is the redirection operator. It will overwrite the file if it exist and create it if it does not exist. But as it is written, the operation is executed for each of the iterations of the loop.
You have two options. The first one is
type nul > pcs.txt
for %%a in (%machines%) do (
echo %%a
echo.
) >> pcs.txt
First create/empty the file and then append data to it. But this will open the file once for each iteration to add the data.
The second option is
(for %%a in (%machines%) do (
echo %%a
echo.
)) > pcs.txt
That is, instead of opening and writting in each iteration, do it only once for the full for loop.

Editing and reading a .txt file with batch commands

I'm trying to create a batch file for an automated testing script.
Everytime the batch for the master script is started up, it should open an existing .txt-file in the same directory containing something like this:
10.200.6.111 inactive
10.200.6.112 inactive
10.200.6.113 inactive
10.200.6.114 inactive
etc...
It should then navigate to the line with it's own IP (which is specified in the batch) and replace the 'inactive' tag with 'active' (indicating that this system has now started testing). Ideally it would also append a timestamp, maybe with something along these lines:
for /f "tokens=1-5 delims=:" %%d in ("%time%") //and then add %%d-%%e at the end?
I've tried to do something similar before and also looked through existing topics but everything seems to be very specific to one situation and I lack the skills to adapt them myself. Ultimately I would need a different .bat to also read from this file and delay any action until all 'active' tabs are gone. But since as of now there is only one vm doing the testing, it would really be a weight off my shoulder if I could just get this to work. Thanks in advance for any help and I apologize if this is a duplicate, I really did try to find something I could use!
PS: I'm really terrible at 'getting' generic code sometimes, it's perfectly possible this has been clearly answered before and I'm just not capable of understanding the solution.
Edit: Just to clarify, it should look roughly like this:
10.200.6.111 inactive
10.200.6.112 inactive
10.200.6.113 active 14:20
10.200.6.114 inactive
etc...
Edit 2: Actually, after thinking about it some more, I've concluded that I can probably do it better by just using my scripts and keeping the batch files simple.
#echo off
setlocal enableextensions disabledelayedexpansion
:: define the log file
set "file=%~nx0.test.txt"
:: generate some lines in log file to test
if not exist "%file%" (
echo 10.200.6.111 inactive
echo 10.200.6.112 inactive
echo 10.200.6.113 inactive
echo 10.200.6.114 inactive
) > "%file%"
:: define the ip of the current node
set "ip=10.200.6.114"
:: change status in log file to active and shows sucess/failure of operation
call :changeStatus "%file%" "%ip%" "active %time:~0,5%"
if errorlevel 1 (
echo failed to change status
) else (
echo status changed
)
:: dump file to see changes
call :dumpFile "%file%"
:: change status in log file to inactive
call :changeStatus "%file%" "%ip%" "inactive"
:: dump file to see changes
call :dumpFile "%file%"
exit /B
:dumpFile file
echo(
echo(----------------------------------
type "%~1"
echo(----------------------------------
echo(
exit /b
:changeStatus file ip status
setlocal enableextensions
set "file=%~1"
set "ip=%~2"
set "status=%~3"
set "retries=0123456789"
set "exitCode=0"
:changeStatusRetry
(>"%file%.lock" (
set "reset=1"
for /f "usebackq tokens=1*" %%a in ("%file%") do (
if defined reset ( set "reset=" & >"%file%" break )
(if "%%a"=="%ip%" (
echo(%%a %status%
) else (
echo(%%a %%b
)) >> "%file%"
)
) && set "exitCode=0" || set "exitCode=1") >nul 2>nul
if "%exitCode%"=="1" (
ping -n 2 localhost >nul 2>nul
set "retries=%retries:~0,-1%"
if defined retries ( set "exitCode=0" & goto changeStatusRetry )
)
del /q "%file%.lock" >nul 2>nul
endlocal & exit /b %exitCode%
This uses a subroutine to handle the changes to log file. It also handles locking on the file from multiple processes. Log file access is locked while changing data and if at the time of changing the file it is locked, it retries up to 10 times to write changed data. On return from the subroutine, errorlevel indicates the result of the operation.

Batch For loop doesn't refresh the file it's pulling from

So I have a for loop that does an iteration of a SQL stored procedure for every line in a file queue.txt, now that all works great, what DOESNT however is that if it is iterating and another line is added to the bottom of the file it uses as it's iteration criteria then it just ignores it.
What I have is this:
#echo off
cd "%UserProfile%\Desktop\Scripting\"
echo words > busy.txt
FOR /f "delims=" %%a in ('type queue.txt') DO (
IF NOT EXIST reset.sql (
::Create SQL command
echo USE dbname> reset.sql
echo EXEC dbo.sp_ResetSubscription #ClientName = '%%a'>> reset.sql
echo EXEC dbo.sp_RunClientSnapshot #ClientName = '%%a'>> reset.sql
echo #################### %date% - %time% ####################################################>> log.txt
echo Reinitialising '%%a'>> log.txt
sqlcmd -i "reset.sql">> log.txt
echo. >> log.txt
echo ####################################################################################################>> log.txt
echo. >> log.txt
type queue.txt | findstr /v %%a> new.txt
type new.txt> queue.txt
echo New list of laptops waiting:>> log.txt
type queue.txt>> log.txt
echo. >> log.txt
echo ####################################################################################################>> log.txt
echo. >> log.txt
if exist reset.sql del /f /q reset.sql
)
)
if exist busy.txt del /f /q busy.txt
if exist queue.txt del /f /q queue.txt
if exist new.txt del /f /q new.txt
So what this does is pulls the file queue.txt and makes an iteration for each of those, now say that it starts with 2 lines in the file, this is great, it starts running the procedures for them.
Now, say I add another line to queue.txt WHILE the loop is running it just ignores that line so it looks like the for doesn't update from the file on each iteration it just imports once.
One way i was thinking to solve this was to count the number of lines at the first iteration of the loop and then check it at the end of each iteration against what it thinks the value should be, and if it is more than it expects then go back to above the for loop (using a goto or some such) but gotos don't work in logic expressions.
Advice anyone please?
#Myles Gray - Your solution has some problems.
First the minor problems:
1) After each iteration of the queue loop, you recreate the queue as the original queue minus the line you are currently working on (you hope! more on that later). After you recreate the queue you append it to your log. That will work, but it seems very innefficient and has the potential of making the log massive and unweildy. Suppose you have a queue with 10,000 lines. By the time you have processed your queue you will have written 99,989,998 queue lines, including 49,994,999 queue lines to your log! That will take a long time to process, even without actually doing your work.
2) You recreate the queue by using FINDSTR, preserving all lines that don't match your current ID. But this will also strip out subsequent lines if they happen to match your current ID. That might not be a problem. But you are doing a substring match. Your FINDSTR will also eliminate subsequent lines that contain your current ID anywhere within it. I have no idea what your IDs look like. But if your current ID is 123, then all of the following IDs will be stripped erroneously - 31236, 12365 etc. That is a potentially devestating problem. I say it is potential because the FOR loop has already buffered the queue, so it doesn't care - unless you abort the loop because new work has been appended to the late.txt file - then you actually will skip those missing IDs! This could be fixed by adding the /X option to FINDSTR. At least then you will only be skipping true duplicates.
Now the major problems - all stemming from the fact only one process can have a file open for any kind of write (or delete) operation.
3) Even though a FOR /F loop does not write to the file, it is designed to fail if the file is actively being written to by another process. So if your FOR loop attempts to read the queue while another process is appending to it, your queue processing script will fail. You have the busy.txt file check, but your queue writer might have already started writing before the busy.txt file has been created. The write operation might take a while, especially if many lines are being appended. While the lines are being written your queue processor could start and then you have your collision and failure.
4) Your queue processor appends the late.txt to your queue and then deletes late.txt. But there is point of time between the append and the delete where a queue writer could append an additional line to late.txt. This late arriving line will be deleted without having been processed!
5) Another possibility is a writer may attempt to write to the late.txt while it is in the process of being deleted by the queue processor. The write will fail, and again your queue will be missing work.
6) Yet another possibility is your queue may attempt to delete late.txt while a queue writer is appending to it. The delete will fail, and you will end up with duplicates in your queue the next time the queue processor appends late.txt to queue.txt.
In summary, concurrency issues can lead both to missing work in your queue, as well as duplicate work in your queue. Whenever you have multiple processes making changes to a file simultaneously, you MUST establish some kind of locking mechanism to serialize the events.
You are already using a SqlServer database. The most logical thing to do is to move your queue out of the file system and into the database. Relational databases are built from the ground up to deal with concurrency.
That being said, it is not too difficult to use a file as a queue within Windows batch as long as you employ a locking strategy. You must make sure both your queue processor and your queue writers follow the same locking strategy.
Below is a file based solution. I'm going to assume you only have one queue processor, and possibly multiple queue writers. With additional work you can adapt the file queue solution to support multiple queue processors. But multiple queue processors is probably easier to implement using the folder based queue that I described at the end of my first answer.
Instead of having the queue writers write to either queue.txt or late.txt, it is easier to have the queue processor rename the existing queue and process it to completion, while the queue writers always write to queue.txt.
This solution writes the current status to a status.txt file. You can monitor your queue processor status by issuing TYPE STATUS.TXT from a command window.
I do some delayed expansion toggling to protect against corruption due to ! in your data. If you know that ! will never appear, then you can simply move the SETLOCAL EnableDelayedExpansion to the top and forgo the toggling.
One other optimisation - it is faster to redirect output just once for a group of statements instead of opening and closing the file for each statement.
This code is totally untested, so there could easily be some silly bugs. But the concepts are sound. Hopefully you get the idea.
queueProcessor.bat
#echo off
setlocal disableDelayedExpansion
cd "%UserProfile%\Desktop\Scripting\"
:rerun
::Safely get a copy of the current queue, exit if none or error
call :getQueue || exit /b
::Get the number of lines in the queue to be used in status updates
for /f %%n in ('find /v "" ^<inProcess.txt') do set /a "record=0, recordCount=%%n"
::Main processing loop
for /f "delims=" %%a in (inProcess.txt) do (
rem :: Update the status. Need delayed expansion to access the current record number.
rem :: Need to toggle delayed expansion in case your data contains !
setlocal enableDelayedExpansion
set /a "record+=1"
> status.txt echo processing !record! out of %recordCount%
endlocal
rem :: Create SQL command
> reset.sql (
echo USE dbname
echo EXEC dbo.sp_ResetSubscription #ClientName = '%%a'
echo EXEC dbo.sp_RunClientSnapshot #ClientName = '%%a'
)
rem :: Log this action and execute the SQL command
>> log.txt (
echo #################### %date% - %time% ####################################################
echo Reinitialising '%%a'
sqlcmd -i "reset.sql"
echo.
echo ####################################################################################################
echo.
)
)
::Clean up
delete inProcess.txt
delete status.txt
::Look for more work
goto :rerun
:getQueue
2>nul (
>queue.lock (
if not exist queue.txt exit /b 1
if exist inProcess.txt (
echo ERROR: Only one queue processor allowed at a time
exit /b 2
)
rename queue.txt inProcess.txt
)
)||goto :getQueue
exit /b 0
queueWriter.bat
::Whatever your code is
::At some point you want to append a VALUE to the queue in a safe way
call :appendQueue VALUE
::continue on until done
exit /b
:appendQueue
2>nul (
>queue.lock (
>>queue.txt echo %*
)
)||goto :appendQueue
Explanation of the lock code:
:retry
::First redirect any error messages that occur within the outer block to nul
2>nul (
rem ::Next redirect all stdout within the inner block to queue.lock
rem ::No output will actually go there. But the file will be created
rem ::and this process will have a lock on the file until the inner
rem ::block completes. Any other process that tries to write to this
rem ::file will fail. If a different process already has queue.lock
rem ::locked, then this process will fail to get the lock and the inner
rem ::block will not execute. Any error message will go to nul.
>queue.lock (
rem ::you can now safely manipulate your queue because you have an
rem ::exclusive lock.
>>queue.txt echo data
rem ::If some command within the inner block can fail, then you must
rem ::clear the error at the end of the inner block. Otherwise this
rem ::routine can get stuck in an endless loop. You might want to
rem ::add this to my code - it clears any error.
verify >nul
) && (
rem ::I've never done this before, but if the inner block succeeded,
rem ::then I think you can attempt to delete queue.lock at this point.
rem ::If the del succeeds then you know that no process has a lock
rem ::at this point. This could be useful if you are trying to monitor
rem ::the processes. If the del fails then that means some other process
rem ::has already grabbed the lock. You need to clear the error at
rem ::this point to prevent the endless loop
del queue.lock || verify >nul
)
) || goto :retry
:: If the inner block failed to get the lock, then the conditional GOTO
:: activates and it loops back to try again. It continues to loop until
:: the lock succeeds. Note - the :retry label must be above the outer-
:: most block.
If you have a unique process ID, you can write it to queue.lock within the inner block. Then you can type queue.lock from another window to find out which process currently has (or most recently had) the lock. That should only be an issue if some process hangs.
You are absolutely correct - A FOR /F loop waits for the command in the IN() clause to finish and buffers the result prior to processing the 1st line. The same is true if you read from a file within the IN() clause instead of executing a command.
Your proposed strategy of counting the number of lines in the queue prior to the FOR loop, and then recounting after the FOR loop has completed could just about work if you stop mucking with the queue contents within the FOR loop. If the final count is greater than the original you could GOTO a :label before the FOR loop and skip the original line count in the FOR loop so you only process the appended lines. But you would still have a concurrency issue if a process writes to the queue while you are getting the line count or if it appends to the queue after you get the final count but before you delete the queue.
There are ways to serialize events within batch when dealing with multiple processes. The key to doing this is to take advantage of the fact that only one process can have a file open for write access.
Code like the following can be used to establish an exclusive "lock". As long as every process uses the same logic, you can guarantee you have exclusive control over one or more file system objects until you release the lock by exiting the block of code.
:getLock
2>nul (
>lockName.lock (
rem ::You now have an exclusive lock while you remain in this block of code
rem ::You can safely count the number of lines in a queue file,
rem ::or append lines to the queue file at this time.
)
)||goto :getLock
I demonstrated how this could work at Re: parallel process with batch. After pressing the link, scroll up to see the original question. It seems like a very similar problem to yours.
You might want to consider using a folder as a queue instead of a file. Each unit of work can be it's own file within the folder. You can use a lock to safely increment a sequence number in a file to be used in naming each unit of work. You can guarantee the unit of work has been completely written by preparing it in a "preperation" folder and only move it to the "queue" folder after it is complete. The advantage to this strategy is that each unit of work file can be moved to an "inProcess" folder while the processing is happening, and then it can be deleted or moved to an archive folder when finished. If the processing fails, you can recover because the file still exists in the "inProcess" folder. You are in a position to know which units of work are unstable (the dead ones in the "inProcess" folder), as well as which units of work have yet to be processed at all (those still in the "queue" folder).
You put in your question "if another line is added to the bottom of the file..."; however, your code does not add a line, but completely replaces the entire file contents (although the new contents just have one new line added):
FOR /f "delims=" %%a in ('type queue.txt') DO (
IF NOT EXIST reset.sql (
. . .
type queue.txt | findstr /v %%a> new.txt
rem Next line REPLACES the entire queue.txt file!
type new.txt> queue.txt
echo New list of laptops waiting:>> log.txt
. . .
if exist reset.sql del /f /q reset.sql
)
)
You may change the method to process queue.txt file by redirecting it into a subroutine that read its lines via SET /P command and a loop assembled with GOTO. This way, the lines that be added to the bottom of queue.txt file inside the read loop will be immediately read when the read process reaches they.
call :ProcessQueue < queue.txt >> queue.txt
goto :EOF
:ProcessQueue
set line=
rem Next command read a line from queue.txt file:
set /P line=
if not defined line goto endProcessQueue
rem In following code use %line% instead of %%a
IF NOT EXIST reset.sql (
. . .
type queue.txt | findstr /v %%a> new.txt
rem Next command ADD new lines to queue.txt file:
type new.txt
echo New list of laptops waiting:>> log.txt
. . .
if exist reset.sql del /f /q reset.sql
)
goto ProcessQueue
:endProcessQueue
exit /B
Of course, if the new lines are added by other processes the new lines will be read and processed by this Batch file automatically.
You must be aware that this method ends at the first empty line in queue.txt file; it also have some restrictions in the characters that it can process.
EDIT: This is a simple example that show how this method work:
set i=0
call :ProcessQueue < queue.txt >> queue.txt
goto :EOF
:ProcessQueue
set line=
set /P line=
if not defined line goto endProcessQueue
echo Line processed: %line% > CON
set /A i=i+1
if %i% == 1 echo First line added to queue.txt
if %i% == 2 echo Second line added to queue.txt
goto ProcessQueue
:endProcessQueue
exit /B
This is queue.txt file at input:
Original first line
Original second line
Original third line
Original fourth line
This is the result:
Line processed: Original first line
Line processed: Original second line
Line processed: Original third line
Line processed: Original fourth line
Line processed: First line added to queue.txt
Line processed: Second line added to queue.txt
Okay so the soultion to my problem that I worked out was to add an extra batch file called co-ordinator.bat it checked if busy.txt was present, if it was then it would add the connecting devices into a file late.txt at the end of each iteration of the loop the process would check for the presence of late.txt, if it was present then it would merge it with queue.txt and then use a goto out of the loop to the top to re-initialise the for loop.
Code as such:
#echo off
cd "%UserProfile%\Desktop\Scripting\"
echo words > busy.txt
:rerun
FOR /f "delims=" %%a in ('type queue.txt') DO (
IF NOT EXIST reset.sql (
::Create SQL command
echo USE dbname> reset.sql
echo EXEC dbo.sp_ResetSubscription #ClientName = '%%a'>> reset.sql
echo EXEC dbo.sp_RunClientSnapshot #ClientName = '%%a'>> reset.sql
echo #################### %date% - %time% ####################################################>> log.txt
echo Reinitialising '%%a'>> log.txt
sqlcmd -i "reset.sql">> log.txt
echo. >> log.txt
echo ####################################################################################################>> log.txt
echo. >> log.txt
type queue.txt | findstr /v %%a> new.txt
type new.txt> queue.txt
echo New list of laptops waiting:>> log.txt
type queue.txt>> log.txt
echo. >> log.txt
echo ####################################################################################################>> log.txt
echo. >> log.txt
if exist reset.sql del /f /q reset.sql
if exist late.txt (
type late.txt>> queue.txt
del /f /q late.txt
goto rerun
)
)
)
if exist late.txt del /f /q late.txt
if exist busy.txt del /f /q busy.txt
if exist queue.txt del /f /q queue.txt
if exist new.txt del /f /q new.txt

Resources