Send commands to a cmd window through a .bat file - Rejected StackOverflow question

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
dbenham
Expert
Posts: 2288
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#1 Post by dbenham » 10 Apr 2016 10:47

Here is a rejected question from StackOverflow: http://stackoverflow.com/q/36527988/1012053
StackOverflow user Bogdan Paicu wrote:So basically i have this Unturned server that's running in a CMD type window. I can input commands to control the server in that window. What I'm trying to do is set up a .bat file that sends commands to the CMD window of the server every, say, 5 minutes.

There are various free Windows utilities available that allow you to send key presses, mouse events, etc. to any window of your choosing. One example is AutoIT - https://www.autoitscript.com/site/autoit/

But I have developed a pure batch solution that is fairly simple to implement. My solution assumes that the controlling batch script is to assume total control of the server window, and the server window is simply looking for commands from stdin.

My demo script simply uses cmd.exe as the "server", but any console based exe could be used.

Your controller script can write commands to a commandFile, and cmd.exe can use redirection to read the commands from the file. But cmd.exe will exit as soon as it reaches the end of file, so something a bit more sophisticated is required.

The solution is to have your server window executing a server batch script that reads the file using SET /P in a loop, and pipes the commands to the actual server. The trick is that SET /P reading from a file will simply return nothing if it is at end of file, without moving the input stream file pointer, and without invalidating the input stream. So if a new line is appended to the file, a subsequent SET /P can properly read it. I did something similar with my TEE.BAT utility.

Note that SET /P is limited a maximum line of 1021 characters. Since I append an extra dummy character to the end of each line, the maximum command length is 1020 characters.

I define a command that will signal the server batch script to quit. The cmd.exe program will automatically close if its input stream is closed, so my server batch script could simply EXIT once it detects the defined quit command. But I'm not sure that all console programs behave that way, so I let the server batch script send a command to the server exe process to quit.

The only other thing is to setup a controller batch script that launches the server, and then enters a loop that sends a command every 5 minutes. Well I didn't want to wait 5 minutes between commands, so my demo waits only 5 seconds between commands. Your controller could be arbitrarily complex. I opted to simply read a list of static commands that are embedded within the script, waiting 5 seconds between each command. The command is sent by simply appending it to the command file that the server is reading. I append an extra character to the end of each command so that I can send an empty string. The server batch script strips off the trailing character before piping it to the actual server.

I could have used two separate batch scripts, one for the controller, and one for the server. But I opted to put everything in a single script. The demo script should be called without any parameters. The script STARTs a new server window that calls itself, but this time with a :Server parameter. The :Server parameter effectively instructs the script to branch to the server code.

When you run the demo script, it will open a new server window. The controller window enters a loop that waits 5 seconds and then reads the next command and sends it. The server window will read and execute each command that is sent. The server window will automatically close once it receives the EXIT command.

Code: Select all

@echo off
if "%~1" neq "" call %1& exit /b

setlocal disableDelayedExpansion

:: Configure the server
set "server=cmd.exe"                        Set to the name (or full path) of your server executable
set "serverTitle=My Server"                 Set to the title for your server window
set "quitCmd=EXIT"                          Set to the command that kills your server
set "cmdFile=%temp%\myServerCommands.txt"   Define a file where server commands should be written
set "delay=5"                               Set to the number of seconds delay between each command

:: Create an empty command file
copy /y nul "%cmdFile%" >nul

call :LaunchServer

:: Command List, with each command preceded by :::
:::echo Hello world!
:::dir
:::pause
:::
:::echo Goodbye
:::exit

:: Controller
for /f "delims=: tokens=*" %%C in ('findstr "^:::" "%~f0"') do (
  timeout %delay%
  echo Sending: %%C
  (echo(%%C#) >>"%cmdFile%"
)
exit /b

:Server
setlocal enableDelayedExpansion
call :ServerLoop <"%cmdFile%"
del "%cmdFile%"
exit

:ServerLoop
set "cmd="
set /p "cmd="
if defined cmd (
  (echo(!cmd:~0,-1!)
  if /i "!cmd:~0,-1!" == "!quitCmd!" exit /b
) else (
  >nul ping localhost /n 2
)
goto :ServerLoop

:LaunchServer
start "%serverTitle%" cmd /c ^""%~f0" :Server ^| "%server%""
exit /b


Dave Benham

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#2 Post by thefeduke » 27 Jun 2016 00:27

This neglected oyster is full of pearls! Only one compliment in almost three months? I added a Set /P loop before the EXIT and enjoyed experimenting with some free-form commands to the slave window.

I have used a file before to communicate a return code and data results from a started window back to the window initiator in the past, but all that I did was to check for the presence of a file and then examined its contents. I kind of like the idea of a slave window spinning on a file for input and a the master spinning on a different file for a slave window completion signal record. There, I just assigned myself another task. I am off to define some triggers and and timeout conditions. Thank you, Dave.

Meanwhile, would you consider posting a failsafe check in the :Serverloop to prevent the script from processing itself as an input command? My patchy code never looks as smooth within your code.

John A.

aGerman
Expert
Posts: 3763
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#3 Post by aGerman » 27 Jun 2016 14:43

That's pretty impressive Dave :!:
Since the interface is a text file I wonder if there is any protection against data races. I don't see that the file was locked if one of the processes access it :?

Regards
aGerman

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#4 Post by thefeduke » 27 Jun 2016 15:26

I very happily added commands to the text file from a different window and they were processed. This can be viewed as an exposure or a feature. I liked it because I have folders that can be written by all in my Homegroup.

John A.

dbenham
Expert
Posts: 2288
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#5 Post by dbenham » 27 Jun 2016 18:41

thefeduke wrote:... would you consider posting a failsafe check in the :Serverloop to prevent the script from processing itself as an input command? My patchy code never looks as smooth within your code.
That is easier said than done. How exactly do you define a match? What if the arguments are different. What if the literal paths are different, but the canonical interpretation is the same? I'm sure it could be done, but...
I'm not so sure this is a good idea. Why shouldn't my CMD.EXE server be allowed to launch a new CMD.EXE process. The server will simply wait until the child CMD.EXE process is terminated.


aGerman wrote:Since the interface is a text file I wonder if there is any protection against data races. I don't see that the file was locked if one of the processes access it :?
I don't think that is an issue with the processes in my code. The flow of information is one directional, and only involves one file. The controller is the only process that writes to the file. The Server batch script is the only process that reads from the file. Reads never block writes. I used to have a concern that the reader could read a partial line before the controller finishes writing the complete line. But I have never seen this happen, so I am assuming it is not an issue.

The only potential problem I see is if the controller writes to the file after the server has received the EXIT command and deleted the file. But that would be a logic error in the controller.

If you allow for outside processes to write to the file, as John (thefeduke) has done, then all bets are off. The controller could fail on a write attempt due to another process having a lock during its own write operation. In this case, a loop would have to be established to retry until the write succeeds.


thefeduke wrote:I very happily added commands to the text file from a different window and they were processed. This can be viewed as an exposure or a feature. I liked it because I have folders that can be written by all in my Homegroup.
Outside interference could be prevented by putting the controller loop in its own block, and redirecting outside the block. Now you guarantee the controller has exclusive write access. You might need the server to check if the final DEL command is successful and loop until it is, just in case the controller has not exited the block before the server attempts to exit.


Dave Benham

aGerman
Expert
Posts: 3763
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#6 Post by aGerman » 27 Jun 2016 19:04

I used to have a concern that the reader could read a partial line before the controller finishes writing the complete line.

Yes that's what I wanted to address. Because SET /P does also read lines without line break I'm not that optimistic. But maybe you're right. Could be there is no EOF yet and it doesn't stop reading until the line break was written in that case.

Regards
aGerman

dbenham
Expert
Posts: 2288
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#7 Post by dbenham » 27 Jun 2016 21:24

Yes, I know that SET /P can read a line without a line break, and it seems like that should be a problem. But I've tried to demonstrate reading a partial line while another process was writing, and I never succeeded. This has always bothered me, but if it ain't broke...

I am very much interested if someone can demonstrate that it is a problem after all.


Dave Benham

jeb
Expert
Posts: 918
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#8 Post by jeb » 28 Jun 2016 01:31

dbenham wrote:Yes, I know that SET /P can read a line without a line break, and it seems like that should be a problem. But I've tried to demonstrate reading a partial line while another process was writing, and I never succeeded. This has always bothered me, but if it ain't broke...


I tested this also for many hours, with different CPU affinities and long writes.
And it's the same for me, I never found a partial line.
I suppose there is a write mechanism, that prevents this type of problem.

jeb

dbenham
Expert
Posts: 2288
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#9 Post by dbenham » 28 Jun 2016 06:45

Thanks jeb. I feel better knowing that you could not detect a problem either.


Dave Benham

aGerman
Expert
Posts: 3763
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#10 Post by aGerman » 28 Jun 2016 11:00

Well then it's an interesting feature too :) Thanks Dave and jeb! I'll keep that in mind.

thefeduke
Posts: 211
Joined: 05 Apr 2015 13:06
Location: MA South Shore, USA

Re: Send commands to a cmd window through a .bat file - Rejected StackOverflow question

#11 Post by thefeduke » 28 Jun 2016 11:40

dbenham wrote:
thefeduke wrote:... would you consider posting a failsafe check in the :Serverloop to prevent the script from processing itself as an input command? My patchy code never looks as smooth within your code.
That is easier said than done. How exactly do you define a match? What if the arguments are different. What if the literal paths are different, but the canonical interpretation is the same? I'm sure it could be done, but...
I'm not so sure this is a good idea. Why shouldn't my CMD.EXE server be allowed to launch a new CMD.EXE process. The server will simply wait until the child CMD.EXE process is terminated
Of course it should, but the fun starts when multiple servers are being feed by the same file and one of the commands within that file starts a new server again using the same file. The new server should use an independent file.
thefeduke wrote:I added a Set /P loop before the EXIT and enjoyed experimenting with some free-form commands to the slave window.
To accomplish this, I had to remove the automated EXIT statement from the script. One sloppy DOSKEY recall and things go wild. The focus changes so rapidly that it it hard to pass the EXIT command.

So, all of the above is about diverging from your design, which remains as beautiful as ever. When the patient explained to the wise doctor that "It only hurts when I do this", the doctor replied "Well, don't do that anymore". If I want to introduce a trap, then it is up to me not to trigger it.

I shall take your guidance out of context
dbenham wrote:Your controller could be arbitrarily complex
and enjoy where it leads.

John A.

Post Reply