Improving Batch File for loop with start subcommand - windows

I've currently got a very simple script which pings 10 IPs:
#ECHO OFF
for /L %%i in (100, 1, 110) DO (
START /W /B cmd /c ping -n 1 192.168.0.%%i | find "time="
)
The output is as expected:
C:\Users\herpderp>test.bat
Reply from 192.168.0.101: bytes=32 time=294ms TTL=64
Reply from 192.168.0.104: bytes=32 time=1ms TTL=64
However, it is VERY slow and definitely happening sequentially. When I run this with a PowerShell Measure-Command I get these results:
PS C:\Users\derpherp> measure-command {start-process test.bat -Wait}
Days : 0
Hours : 0
Minutes : 0
Seconds : 23
Milliseconds : 107
Ticks : 231074173
TotalDays : 0.000267446959490741
TotalHours : 0.00641872702777778
TotalMinutes : 0.385123621666667
TotalSeconds : 23.1074173
TotalMilliseconds : 23107.4173
So we see it is taking ~23 seconds to execute.
Now, if I were on a Linux system and wanted to do this same thing, I can do the following:
#!/bin/bash
for ip in $(seq 100 110); do
ping -c 1 192.168.0.$ip | grep "bytes from" | cut -d" " -f4 | cut -d ":" -f1 &
done
The result is different in a variety of ways:
The results aren't always sequential, meaning it actually started the commands more asynchronously:
root#kali:~/vids/bash_scripting# ./test.sh
192.168.0.104
192.168.0.100
192.168.0.103
192.168.0.101
The time for it to display all of the positive results is typically less than a second and this scales to much larger sets of numbers.
So, my question, is this a limitation of batch scripting? I find it hard to believe that bash performs literally 100s of times better than Windows CMD interpreter for such a simple task?
If this is limited, does PowerShell offer a competitive way to create tasks? I've used Start-Job in a foreach loop but that seems to become unworkable for large numbers of tasks (ie Test-Connection on a /16 network)
Edit 1
#ECHO OFF
for /L %%i in (100, 1, 110) DO (
ping -n 1 192.168.0.%%i | find "time="
)
This outputs the current window and takes just as long as the initial variant
#ECHO OFF
for /L %%i in (100, 1, 110) DO (
START ping -n 1 192.168.0.%%i | find "time="
)
This outputs to unique windows per IP and takes just as long to complete.

Asynchronous isn't easy, but with jobs you can get close pretty easily with PowerShell.
This code should behave how it looks like your bash code does, returning the IP address of all hosts that respond without error:
$IPAddresses = 100..110 | ForEach-Object { "192.168.0.$_"; }
$JobList = $IPAddresses | ForEach-Object {
Test-Connection -ComputerName $_ -Count 1 -AsJob;
}
Receive-Job -Job $JobList -AutoRemoveJob -Wait | `
Where-Object { $_.StatusCode -eq 0 } | `
Select-Object -ExpandProperty Address;
The above completes for me against ~250 hosts in 3 seconds.
Test-Connection itself claims to use up to 32 simultaneous connections (configurable with -ThrottleLimit setting) but it sure seems like the -Delay option completely overrides that setting if you're targeting a wide range.
You may have issues with PowerShell v4 and earlier, as it may behave slightly differently. Test-Connection in particular seems to be idiosyncratic on different versions of Windows or PowerShell. At least, I always remember it throwing an error instead of returning an error status code in previous versions.
If you want more fine grain control over the ping than Test-Connection gives you -- for example, it defaults to a 1 second timeout so the minimum time you'll wait when any host is down is 1 second -- you'll probably have to revert to directly calling Get-WMIObject -Query "SELECT * FROM Win32_PingStatus WHERE Address = '$IP' AND TimeOut = 200" -AsJob.

Seems that I have found a pure batch-file solution. Basically, the script fires several ping commands simultaneously, which all write their result to individual log files; these files are monitored for write-access, because the files are write-locked as long as the respective ping processes are ongoing; as soon as write-access is granted, the first lines of the log files containing the IP addresses are copied into a summary log file. So here is the code -- see all the explanatory rem remarks:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Predefine IP addresses as an array:
for /L %%J in (0,1,5) do (
set "IP[%%J]=127.0.0.%%J"
)
rem // Ping IP addresses simultaneously, create individual log files:
for /F "tokens=2,* delims=[=]" %%J in ('set IP[') do (
start "" /B cmd /C ping -n 2 %%K ^| find "TTL=" ^> "%~dpn0_%%J.log"
)
rem // Deplete summary log file:
> "%~dpn0.log" rem/
rem /* Polling loop to check whether individual log files are accessible;
rem this works, because a pinging process is not yet finished, the
rem respective log file is still opened and therefore write-locked: */
:POLL
rem // Give processor some time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished
rem pinging process, the related array element becomes deleted, so
rem finally, there should not be any more array elements defined: */
for /F "tokens=2 delims=[=]" %%J in ('2^> nul set IP[') do (
rem // Suppress error message in case log file is write-locked:
2> nul (
rem // Try to append nothing to the log file:
>> "%~dpn0_%%J.log" rem/ && (
rem /* Appending succeeded, hence log file is no longer locked
rem and the respective pinging process has been finished;
rem therefore read the first line containing the IP: */
set "FILE=" & set "IP="
< "%~dpn0_%%J.log" set /P IP=""
rem // Copy the read line to the summary log file:
if defined IP >> "%~dpn0.log" call echo(%%IP%%
rem // Undefine related array element:
set "IP[%%J]="
rem // Store log file path for later deletion:
set "FILE=%~dpn0_%%J.log"
)
rem // Delete individual log file finally:
if defined FILE call del "%%FILE%%"
)
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set IP[ && goto :POLL
endlocal
exit /B

You have no need of start command. You turn off all it's features. Then you tell it to start a new process for CMD which is slow and unnecessary. So - ping -n 1 192.168.0.%%i |find "time=".
See my summary here of how to start programs - What do all commands in batch mean and do?.
You can use start to run ping in parallel, but you need to remove /w which means wait (but it's being ignored because) and /b which says don't run the command in a new spawned process but do it sequentially.
As you are still doing multiple process creation of Ping you can use WMIC to avoid that and test all computers in one process create.
wmic /node:#"c:\computerlist.txt" /append:"textfile.txt" path win32_pingstatus where "address='127.0.0.1' and responsetime > 100" get responsetime,timestamprecord.
So we've gone from 20 process creates to 1.
In programming we have to pay taxes of system overhead. Windows collect most tax at Process Creation and Window Creation. This is to allow other operations to take place without as much system overheads. An example is Windows keeps tables of processes that are running - uses resources to update when a process is created but allows quick lookup for other operations.

Related

net use in cmd script hangs forever - how to set a timeout if server denies connection

I have a Windows batch file which connects several network drives. It first pings the servers, and if that succeds, it then tries to map the share with net use ...:
SET host=mini3
IF /I NOT %host% == %COMPUTERNAME% (
ping -4 -n 2 -w 1000 %host% | find "TTL=" > NUL
IF !ERRORLEVEL! EQU 0 (
echo Q: == \\%host%\Q_ -- mini3-Volumes
net use Q: \\%host%\Q_ /persistent:yes > NUL && echo OK
) ELSE (
echo %host% not found. Skipping.
)
)
Sometimes, even though the machine is reachable by ping, the net use command hangs forever. The reason it hangs seems due to a misconfiguration in the SMB server, and/or in the Windows Credentials stored on the client.
So when that happens, for whatever reason, I would like to print an error and go on to the next drive to map.
The Windows Timeout command seems to actually be a "delay" command, equivalent of Unix sleep, and not a timeout.
Is there a good way to have a real timeout for a net use command, without aborting the entire .cmd script?
With a few mods I have no problem
key was correcting %errorlevel% as no runtime delay was included/needed
the drive mappings are spawned independently so the tasks can progress
There should be a brief flash if all is well, otherwise if share is not found they should timeout naturally.
Advent is in my case another workstation rather than server. Change that back to mini3
#echo off
SET "host=Advent"
IF /I NOT "%host%"=="%COMPUTERNAME%" (
ping -4 -n 2 -w 1000 %host% | find "TTL=" > NUL
IF %ERRORLEVEL% EQU 0 (
echo Q: == \\%host%\Q_ -- %host%-Volumes
START "Z Drive" net use Z: \\%host%\users /persistent:yes
START "Q Drive" net use Q: \\%host%\Q_ /persistent:yes
REM surplus from above line > NUL && echo OK
) ELSE (
echo %host% not found. Skipping.
)
)
rem delay this file so it waits to let the new connections process above
timeout 2
net use | find ":"

Batch - Ping on Multiple sites

So I want to ping, 20 different sites 15 consecutive times with the same data size and take their Average RTT into a file. But I must also check the same IP for different sizes of ping data, from 8 to 2048 every time multiplied with 2 (8, 16, 32, 64 etc).
I tried to develop my own code and here is what I have so far.
#echo off
CLS
ping -n 1 %1|find "Reply" > nul
CLS
IF %ERRORLEVEL% == 0 ( FOR %%G IN (8,16,32,64,128,256,512,1024,2048) DO (echo Pinging with %%G byte of data... & ping -n 1 -l %%G %1|findstr /x "^.*Request.* ^.*Lost.* ^.*Average.*ms" >> results.txt)) ELSE (ECHO The Server %1 did not respond)
For easier reading:
#echo off
CLS
ping -n 1 %1
CLS
IF %ERRORLEVEL% == 0
(
FOR %%G IN (8,16,32,64,128,256,512,1024,2048) DO
(
echo Pinging with %%G byte of data... &
ping -n 15 -l %%G %1|findstr /x "^.*Request.* ^.*Lost.* ^.*Average.*ms" >> results.txt
)
ELSE
( ECHO "The Server did not respond" )
What I want to accomplish is, a batch file that, accepts 10 (or 15 if it is possible) parameters that are websites ping all of them with 8, 16, 32, ..., 2048 for 15 times each site and also if the server for any reason in some of my ping requests gives me a Request time out, I want to stop the loop for this parameter and continue with the next one but I want it to write to the file that "Request Time Out"
In my code, I am trying to check if the server responds on my ping requests, if I get a reply then begin my FOR loop.
Although, I can't check at any given time if the server still accepts my pings, this happens more often when I have packets of 2048 bytes. If the server give me a "NO" then I get a "Request time out".
So with some kind of code implementation I want to check if the command
ping -n 15 -l %%G %1|findstr /x "^.*Request.* ^.*Lost.* ^.*Average.*ms"
will print Request Time Out so I can stop the ping and move on the next parameter, so it won't bother pinging the same IP 15 times.
Also, with the above implementation I won't have to check at the start of the script if the server responds because it will do it inside the FOR loop.
I believe that the parameter switching is not that hard to implement, it's just a shift and some labels.
Thank you in advance for your time, I will be glad to give you more explanations on the commends below!

What's the best way to stop a batch script after a specified amount of time?

I'm writing a batch script where I want the user to be able to control how long the script runs. When running it from the command line, the user will pass in a switch like this:
./myscript --stop-after 30
which means that the script will keep doing its job, and check every iteration how much time has passed. If more than a half a minute has passed, then it'll quit. How would I implement this in a batch script?
For reference, here is the code I have so far:
:parseArgs
if "%~1" == "" goto doneParsing
if /i "%~1" == "--stop-after" (
shift
set "duration=%~1"
)
:: Parse some other options...
shift
goto parseArgs
:doneParsing
:: Now do the actual work (psuedocode)
set "start=getCurrentTime"
set "end=%start% + %duration%"
while getCurrentTime < %end% (
:: Do some lengthy task...
)
How would I go about implementing the latter part of the script, after parsing the options?
Thanks for helping.
This is not that trivial. You'll have to do a lot of calculation within your script to cover all cases of full minute, full hour, or even new day. I can think of two different ways. Both are based on two batch files:
1. Termination via taskkill
starter.bat:
#echo off
if "%1"=="" (
set duration=5
) else (
set duration=%1
)
start "myscript" script.bat
ping 127.0.0.1 -n %duration% -w 1000 > nul
echo %duration% seconds are over. Terminating!
taskkill /FI "WINDOWTITLE eq myscript*"
pause
script.bat:
#echo off
:STARTLOOP
echo doing work
ping 127.0.0.1 -n 2 -w 1000 > nul
goto STARTLOOP
For this solution, it's important that you give the window executing your script a unique name inside the line start "myscript" script.bat. In this example, the name is myscript. taskkill /FI "WINDOWTITLE eq myscript*" uses myscript to identify which process to terminate.
However, this might be a bit dangerous. Your script will be killed after x seconds, no matter if an iteration is done or not. So, e.g., write access would be a bad idea.
2. Termination via flag file
starter.bat:
#echo off
if "%1"=="" (
set duration=5
) else (
set duration=%1
)
if exist terminationflag.tmp del terminationflag.tmp
start script.bat
ping 127.0.0.1 -n %duration% -w 1000 > nul
echo %duration% seconds are over. Setting termination flag!
type NUL>terminationflag.tmp
script.bat:
#echo off
:STARTLOOP
echo doing work
ping 127.0.0.1 -n 2 -w 1000 > nul
if not exist terminationflag.tmp goto STARTLOOP
del terminationflag.tmp
echo terminated!
Here, it's important to ensure that your script is allowed to create/delete a file at the current location. This solution is safer. The starter script will wait the given amount of time and then create the flag file. Your script will check after each full iteration whether the flag is there or not. If it's not, it will go on—if it is, it will delete the flag file and terminate safely.
In both solutions ping is used as timeout function. You could also use timeout/t <TimeoutInSeconds> if you are on Windows 2000 or later. However, timeout doesn't always work. It will fail in some scheduled tasks, on build servers, and many other cases. You'd be well advised to stick to ping.

Echoing output of command from variable

I'm trying to create a simple batch script that stores the output of a command to tmp file, stores the content of the tmp file to a variable, and outputs the variable:
#setlocal enableextensions enabledelayedexpansion
#echo off
ping -n 1 10.1.0.2 > tmp
SET #var= < tmp
ECHO %#var%
del tmp
I would expect the above to work, but it outpus:
C:\Documents and Settings\Administrator\Desktop>pinger.bat
ECHO is off.
(Nb: taking the #echo off just outputs ECHO is on, including echoing all the lines of code)
The question pointed by #rostok (my answer here), shows the reason to not use the received packets to determine if the host is or not online. On the same subnet, over ipv4, with an offline host, you will get a "unreachable" error, but the packets are received.
For a simple test in ipv4, the linked answer can handle your problem. For a more robust test over ipv4 or ipv6 (that show different behaviour), this could help.
Anyway, the code to get the required data directly from the ping command,
set "received="
for /f "skip=6 tokens=5 delims==, " %%a in ('ping -n 1 10.1.0.2') do (
if not defined received set "received=%%a"
)
or from the file
set "received="
for /f "usebackq skip=6 tokens=5 delims==, " %%a in ("tmpFile") do (
if not defined received set "received=%%a"
)
Where the skip clause indicates that the first six lines should be skipped, and the tokens and delims select the required element inside the line
Packets: Send = 1, Received = 1,
^ ^^^ ^^ ^^^ ^ Delimiter position
1 2 3 4 5 Token number
But as said, this is not a reliable way to solve the problem

Batch ERRORLEVEL ping response

I'm trying to use a batch file to confirm a network connection using ping. I want to do batch run and then print if the ping was successful or not. The problem is that it always displays 'failure' when run as a batch. Here is the code:
#echo off
cls
ping racer | find "Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),"
if not errorlevel 1 set error=success
if errorlevel 1 set error=failure
cls
echo Result: %error%
pause
'racer' is the name of my computer. I'm having my computer ping itself so I can eliminate the variable of a poor connection. As I said before, the batch always results in failure. Oddly enough, the program works fine if I copy the code into the command prompt. Does anyone know why the program works fine in the command prompt but doesn't work as a batch?
Thanks
A more reliable ping error checking method:
#echo off
set "host=192.168.1.1"
ping -n 1 "%host%" | findstr /r /c:"[0-9] *ms"
if %errorlevel% == 0 (
echo Success.
) else (
echo FAILURE.
)
This works by checking whether a string such as 69 ms or 314ms is printed by ping.
(Translated versions of Windows may print 42 ms (with the space), hence we check for that.)
Reason:
Other proposals, such as matching time= or TTL are not as reliable, because pinging IPv6 addresses doesn't show TTL (at least not on my Windows 7 machine) and translated versions of Windows may show a translated version of the string time=. Also, not only may time= be translated, but sometimes it may be time< rather than time=, as in the case of time<1ms.
If you were to
echo "Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),"
you would see the % is stripped. You need to escape it as % has a special meaning within a batch file:
"Packets: Sent = 4, Received = 4, Lost = 0 (0%% loss),"
However its simpler to use TTL as the indication of success;
.. | find "TTL"
Testing for 0% loss may give a false positive, in this scenario:
Let's say you normally have a network drive on some_IP-address, and you want to find out whether or not it's on.
If that drive is off, and you ping some_IP-address, the IP address from which you ping, will respond:
Answer from your_own_IP-address: target host not reachable
... 0% loss
You might be better off using if exist or if not exist on that network location.
I 'm not exactly sure what the interaction between FIND and setting the error level is, but you can do this quite easily:
#echo off
for /f %%i in ('ping racer ^| find /c "(0%% loss)"') do SET MATCHES=%%i
echo %MATCHES%
This prints 0 if the ping failed, 1 if it succeeded. I made it look for just "0% loss" (not specifically 4 pings) so that the number of pings can be customized.
The percent sign has been doubled so that it's not mistaken for a variable that should be substituted.
The FOR trick serves simply to set the output of a command as the value of an environment variable.
Another variation without using any variable
ping racer -n 1 -w 100>nul || goto :pingerror
...
:pingerror
echo Host down
goto eof
:eof
exit /b
Yes ping fails to return the correct errorlevel. To check the network connection and the computer I used "net view computername" then checked %errorlevel% - simple and easy
First of all
>#echo off
>for /f %%i in ('ping racer ^| find /c "(0%% loss)"') do SET MATCHES=%%i
>echo %MATCHES%
Does not work. If it won't fail, it will detect 0%, because it has 0%.
If it fails, does not work either, because it will have 100% loss, which means, it found the 0% loss part behind the 10
10(0% loss)
Have it detect for 100% loss like so:
>for /f %%i in ('ping -n 1 -l 1 %pc% ^| find /c "(100%% loss)"') do SET check=%%i
Errorlevel might be a bit messed up, but it works like a charm:
>if '%check%'=='1' goto fail
>if '%check%'=='0' echo %pc% is online.&goto starting
1 means it failed
0 means it succeeded
In my script is use links.
Goto fail will go to :fail in my script which will message me that %pc% (which I'll have the user input in the beginning) is offline and will go for another run.
>:fail
>color 0c
>title %pc% is offline
>echo %pc% is offline
>PING -n 6 127.0.0.1>nul
>goto choice
I hope this helps.
The most simple solution to this I can think of:
set error=failure
ping racer -n 1 -w 100>nul 2>&1 && set error=success
Of course, -w needs to be adjusted if on a slow link (100ms might be too short over Dialup ;-))
regards
ping has an errorlevel output value. Success is 0, failure is 1.
Just do this:
C:\>ping 4.2.2.2
Pinging 4.2.2.2 with 32 bytes of data:
Reply from 4.2.2.2: bytes=32 time=28ms TTL=57
Reply from 4.2.2.2: bytes=32 time=29ms TTL=57
Reply from 4.2.2.2: bytes=32 time=30ms TTL=57
Reply from 4.2.2.2: bytes=32 time=29ms TTL=57
Ping statistics for 4.2.2.2:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 28ms, Maximum = 30ms, Average = 29ms
C:\>echo %errorlevel%
0
C:\>ping foo.bar
Ping request could not find host foo.bar. Please check the name and try again.
C:\>echo %errorlevel%
1
As you can see there is no need for all this scripting overkill.
Based on Alex K's note, this works for me on Windows 7:
#echo off
setlocal enableextensions enabledelayedexpansion
for /f %%i in (PCS.TXT) do (
SET bHOSTUP=0
ping -n 2 %%i |find "TTL=" > NUL && SET bHOSTUP=1
IF !bHOSTUP! equ 1 (
CALL :HOSTUP %%i
) else (
CALL :HOSTDOWN %%i
)
)
GOTO EOF
:HOSTUP
echo Host UP %1
GOTO EOF
:HOSTDOWN
echo Host DOWN %1
GOTO EOF
:EOF
exit /B
ping 198.168.57.98 && echo Success || echo failed
I liked the concept of the FIND in the ping results but why not just FIND the Reply from the Address being pinged?
In the example below I enter an IP address as a variable, PING that IP, then look for that variable in the reply string, using the FIND Command.
If the Reply String contains anything other than the correct IP it reports failure.
If you want you can just read the value of ERRORLEVEL from the FIND.
That will give you a reliable value to work with.
#echo off
Set /P IPAdd=Enter Address:
cls
ping %IPAdd% | find "Reply from %IPAdd%:"
if not errorlevel 1 set error=success
if errorlevel 1 set error=failure
cls
echo Result: %error%
pause
I needed to reset a wifi connection because it has issues. This was my quick solution.
#echo off
Rem Microsoft Windows 10 ping test to gateway.
Rem Run batch file from an administrative command prompt.
cls
:starting
Rem Send one ping to the gateway. Write the results to a file.
ping 192.168.1.1 -n 1 > pingtest.txt
Rem Search for unreachable in the file.
c:\windows\system32\findstr.exe "unreachable" pingtest.txt
Rem errorlevel 0 reset the adapter if 1 then wait 10 minutes and test again
if %errorlevel%==1 goto waiting
Rem unreachable was found reset the adapter.
Rem write the date and time the reset was done.
echo Reset date: %date% time: %time% >> resettimes.txt
Rem issue netsh interface show interface to find your adapter's name to reset
Rem my adapter is "wi-fi"
netsh interface set interface "wi-fi" disable
timeout /t 5
netsh interface set interface "wi-fi" enable
:waiting
echo "It is online waiting 10 minutes"
timeout /t 600
goto starting

Resources