going_28
going_28

Reputation: 3

batch FOR loop piped command

Basically I'm trying to read from a text file of servers, ping them continuously, and output a timestamp when each server has rebooted (find "Request timed out.")

for /f "delims=" %%a in (Servers_List.txt) do (
     start cmd /k ping -t %%a | find "Request timed out."
) && (Echo %%a rebooted at %time%.)

It launches each server ping -t in a separate window until I add the | find piece, and then it only launches one at a time after each subsequent window is closed.

Does anyone know a way to run each CMD window simultaneously without needing to close each prior window?

Thank you!

Upvotes: 0

Views: 1558

Answers (1)

dbenham
dbenham

Reputation: 130829

Your design cannot work :-(

Your first problem is you are piping the output of your START command, but you want to pipe the output of PING. That can be fixed by escaping the pipe as ^|.

You have a similar problem with your conditional command concatenation - you would need ^&^& instead of &&1.

But the biggest problem is your ECHO will not execute until the pipe is closed, which is never! FIND will continue to look for additional timeout lines after the first one is detected. It will not end until after the pipe is closed, and your ECHO will not run until after FIND terminates.

Your best bet would be to write a VBScript, or JScript, or PowerShell script to continuously ping a server, and write out your message with timestamp when timeout is detected.

You could write a batch script to loop through your list of servers and start a new custom ping process for each one. Or you could put everything into a single VBScript (or JScript, or Powershell) script.

Perhaps one of the following links can help get you started:

http://windowsitpro.com/scripting/how-can-i-use-vbscript-script-ping-machine

http://www.sems.org/2013/07/little-vbscript-to-continously-ping-a-host-with-timestamplog/

https://thwack.solarwinds.com/docs/DOC-135033

I am assuming you ideally would like to have all output in a single screen. You can use the START /B option to run multiple processes within the same console window, but then you run the risk of garbled output if two processes attempt to write to the console at the same time. This can be solved via the exclusive lock that is obtained when a process opens a file for writing. See How do you have shared log files under Windows? for some pointers on how this can help. That link is for shared log files, but it is easily adapted for shared console access.

I have used my JREPL.BAT utility to hack up a solution. Architecturally this is a convoluted abomination, but it works! It runs an endless PING process for each server, all in parallel within the same console. Each process pipes its output to JREPL which detects if the server responded and writes out a time-stamped message to both the console and a log file. The log file is also used as a lock file to coordinated shared access between the processes. Each process keeps track of the last server status, and a message is only written when the status changes.

@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment

::***** Batch code *******
@echo off
setlocal enableDelayedExpansion
set "first="
copy nul serverStatus.log >nul
for /f "delims=" %%A in (Servers_List.txt) do if not defined first (
  set "first=%%A"
) else (
  start /b cmd /c ping -t %%A ^| jrepl "^Request timed out|^Reply from " "log('%%A','DOWN')|log('%%A','UP')" /t "|" /jmatch /jlib "%~f0"
)
if defined first ping -t %first% | jrepl "^Request timed out|^Reply from " "log('%first%','DOWN')|log('%first%','UP')" /t "|" /jmatch /a /jlib "%~f0"
exit /b

****** JScript code ******/
var status = 'initial';
var fso = new ActiveXObject("Scripting.FileSystemObject");
var file;

function log( server, newStatus ) {
  if (status!=newStatus) {
    status=newStatus;
    var msg=new Date().toString()+'  '+server+' is '+newStatus;
    while (!openFile());
    file.WriteLine(msg);
    output.WriteLine(msg);
    file.Close();
  }
  return false;
}

function openFile() {
  try {
    file = fso.OpenTextFile('serverStatus.log',8);
    return true;
  } catch(e) {
    return false;
  }
}

But, I don't see why it is necessary to run the processes in parallel. It seems to me you can simply enter an endless loop that cycles through the servers, and PINGs each server individually, writing out the status. Here is a simple batch script that does just that. Again, it only prints out a message when it detects a change in status.

@echo off
setlocal enableDelayedExpansion
for /l %%N in () do for /f "delims=" %%S in (Servers_List.txt) do (
  ping /n 1 %%S | findstr /bc:"Request timed out" >nul && set "status='DOWN'" || SET "status=UP"
  if "!status!" neq "!%%S!" (
    set "%%S=!status!"
    echo !date! !time!  %%S is !status!
  )
)

Upvotes: 1

Related Questions