0

I want to drag & drop a file onto a batch script, which will print/echo some text, then call a long-running (exited with Ctrl-C) command-line executable that prints to stdout, with the dropped file as argument, in a cmd.exe window; when Ctrl-C is pressed, the executable should exit, but the cmd.exe should pause, showing "Press any key to continue" - and when "any key" is pressed, the started cmd.exe finally exits, and its window gets closed. However,

Here is my attempt at a reproducible example/test case: let's say we're in C:\tmp\test_bat (easier to reproduce than network path, but let's imagine we're at network path regardless).

As the input file to drag&drop, let's have mytest.txt:

Hello world!
Lorem ipsum dolor sit amet ...
Can't think of anything else, really ...

To demonstate a long-running command line program that needs to be interrupted with Ctrl-C, we could have used C:\Windows\System32\PING.EXE called with arguments like (say) -t yahoo.com - except there is no meaningful input file there, so drag&drop cannot be really demonstrated with it.

So instead, let's compile the following (extremely simple, no proper checks) C program, text_looper.c ... :

#include <stdio.h>
#include <time.h>

#ifdef _WIN32 // https://stackoverflow.com/q/14818084
#include <Windows.h>
#else
#include <unistd.h>
#endif
void do_sleep(unsigned int sleep_time_ms) {
  #ifdef _WIN32
  Sleep(sleep_time_ms);
  #else
  usleep(sleep_time_ms*1000);
  #endif
}

#define BUFSIZE 1000
char buff[BUFSIZE];
char *line_read = NULL;

int main(int argc, char *argv[]) {
  char* input_file = argv[1];
  FILE* file_ptr = fopen(input_file, "r");
  printf("Got file: '%s' ; looping... (exit with Ctrl-C)\n\n", input_file);
  unsigned int loop_counter = 1;
  while( 1 ) { // infinite loop
    while( line_read = fgets(buff, sizeof(buff), file_ptr) ) {
      // trim newline
      line_read[strcspn(line_read, "\r\n")] = 0;
      // only carriage return, so left-pad counter, right-pad line
      printf("\r%10d: %-50s", loop_counter, line_read);
      fflush( stdout );
      loop_counter++;
      do_sleep(250);
    }
    fseek(file_ptr, 0, SEEK_SET);
    printf("\r%-62s", "(again ...)");
    do_sleep(250);
  }
  fclose(file_ptr);
}

... as per Walkthrough: Compile a C program on the command line | Microsoft Learn: so open a Developer Command Prompt, and run:

cd C:\tmp\test_bat
cl text_looper.c

... and we obtain text_looper.exe (for a quick test: text_looper.exe mytest.txt).

This is as far as I got with a batch file call_text_looper.bat:

@echo off

set "LOOPER_EXE=C:\tmp\test_bat\text_looper.exe"
set "INPUT_FILE=%~1"
for /F %%i in ("%LOOPER_EXE%") do set "EXE_BASENAME=%%~ni"

echo.
echo Creating temporary drive with this .bat file location, and changing current working dir there ...
pushd %~dp0
echo.
echo CWD IS %cd% ; (path to batch dir) is %~dp0
echo.
echo Program: %EXE_BASENAME% (%LOOPER_EXE%) ..
echo Using %INPUT_FILE% as input for program ..
echo.

set "WINDOW_TITLE=call: %EXE_BASENAME%"
start "%WINDOW_TITLE%" cmd /k "%LOOPER_EXE% %INPUT_FILE%"

echo Removing temporary drive ...
popd
echo. & pause

The problem with this call_text_looper.bat is, that:

  • I get two cmd.exe windows opened - one for the .bat itself, one for the resulting child cmd.exe process (the "subshell", if you will)
    • When I press Ctrl-C in the "subshell" window, program exits -- but then I get cmd.exe prompt, I do not get pause with "Press any key to continue" that would close the window ... So I have to close that window manually (the parent .bat window exits upon "Press any key to continue" as expected)
  • once we call with start ..., the "subshell" gets started "in parallel"/non-blocking, and the start ... command returns immediately, and we proceed directly to "Removing temporary drive"; so:
    • if you test this batch script in C:\tmp\test_bat, it will at least run the program in the "subshell" window because you're on a local drive; but
    • if you try it from a network location, you will get something like:
    The current directory is invalid.
    
    V:\path\to\test_bat>
    
    ... because the temporary drive got unmounted by the parent .bat script, by the time the "subshell" got to use it.

So, how can I get a .batch script that will call the program with argument as per my reqiurements above?

(PS: I thought of defining the preamble (the starting part with echoes and drive mounting) as a function, and have the "subshell" call it instead - but this is BATCH, there are no "functions" here, only labels that you can JUMP to ( https://superuser.com/questions/651936/batch-file-function-is-executed-without-being-called ) - and the child process/"subshell" expectedly does not inherit these labels).

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 1
    People probably got flustered by the wall of text. Also, there absolutely are functions, they're just called "subroutines" in this language and you use them with the `call` command. – SomethingDark Feb 12 '23 at 18:44
  • Thanks @SomethingDark - I looked up subroutines, and found https://stackoverflow.com/questions/3713601 where they say "*Batch files are not structured programs; they are a sequence of instructions with some BASIC-like facility for GOTO and CALL.*" and even there, they use labels for implementing subroutines - so I guess my ending comment still holds. – sdbbs Feb 12 '23 at 18:55
  • 1
    I'm wondering why you're using ```start "%WINDOW_TITLE%" cmd /k "%LOOPER_EXE% %INPUT_FILE%"``` over ```Start "%WINDOW_TITLE%" %SystemRoot%\System32\cmd.exe /D /C ""%LOOPER_EXE%" "%INPUT_FILE%""```, or whether you tried the same with ```Start /B```, or even without `Start`, (```%SystemRoot%\System32\cmd.exe /D /C ""%LOOPER_EXE%" "%INPUT_FILE%""```), or `cmd.exe`, (```"%LOOPER_EXE%" "%INPUT_FILE%"``` or ```Start "%WINDOW_TITLE%" "%LOOPER_EXE%" "%INPUT_FILE%"```, or ```Start "%WINDOW_TITLE%" /B "%LOOPER_EXE%" "%INPUT_FILE%"```). What is the reasoning behind starting a parallel process like that? – Compo Feb 12 '23 at 19:10
  • Thanks @Compo - the start command I found as such at the link explaining that window title is required for manual size; I did not want a parallel process, I just did not know any better. From your suggestions: `cmd /K` I already use; I would have used `cmd /C` if I knew how to insert a `pause` at the end of command; `cmd /D` has something to do with registry and I cannot see how that applies; `start /B` is interesting, as it would start without a window - but I do want (one) window, except I need to set a custom title for it. – sdbbs Feb 12 '23 at 20:01
  • 1
    The only purpose of a Window Title, really, is if you want to identify the process using it, and then do something. Otherwise it has no benefit. You could very easily use the `Title` command, if you just wanted to look at something to recognize. The `/D` option with `cmd.exe`, should IMO always be used, to prevent any pre-commands being run, via the registry, which were never intended. What you should do is to explain what you are really doing, instead of introducing a pointless looping executable, which is not representative of the actual task. With that, we may be able to advise you better. – Compo Feb 12 '23 at 21:09
  • @Compo - well, I want to drag & drop a file on a bat script, stored on a network drive, that will open my custom USB .exe program with settings in that file, and then connect to a device and dump output to a terminal until Ctrl-C (which is pointless to post as a program, as no-one else has that device, so no one else can reproduce the problem), and have one window opened, which will automatically position itself at a position I specify, and keep going until I press Ctrl-C to stop the program or program crashes, and then ask me "Press any key" to finally close the window. Is that better? – sdbbs Feb 12 '23 at 21:37

1 Answers1

0

EDIT: improvement to the original answer (still below) - here also a temporary Powershell script is generated with the sole purpose of moving the window at the coordinates specified:

@echo off

set "LOOPER_EXE=C:\tmp\test_bat\text_looper.exe"
set "INPUT_FILE=%~1"
for /F %%i in ("%LOOPER_EXE%") do set "EXE_BASENAME=%%~ni"

set "WINDOW_TITLE=call: %EXE_BASENAME%"
title %WINDOW_TITLE%

echo.
echo Creating temporary drive with this .bat file location, and changing current working dir there ...
pushd %~dp0
echo.
echo CWD IS %cd% ; (path to batch dir) is %~dp0
echo.


:: generate (https://stackoverflow.com/q/1015163) handling quotes (https://stackoverflow.com/q/804646)
:: PowerShell for moving window (https://stackoverflow.com/q/43043972)

> movewindow.ps1 (
@echo.Add-Type -Name Window -Namespace Console -MemberDefinition '
@echo.[DllImport^("Kernel32.dll"^)]
@echo.public static extern IntPtr GetConsoleWindow^(^);
@echo.[DllImport^("user32.dll"^)]
@echo.public static extern bool MoveWindow^(IntPtr hWnd, int X, int Y, int W, int H^); '
@echo.#$consoleHWND
@echo.$consoleHWND = [Console.Window]::GetConsoleWindow^(^);
@echo.[Console.Window]::MoveWindow^($consoleHWND,300,0,800,400^);
)
endlocal
powershell .\movewindow.ps1
del .\movewindow.ps1


echo.
echo Program: %EXE_BASENAME% (%LOOPER_EXE%) ..
echo Using %INPUT_FILE% as input for program ..
echo.

%LOOPER_EXE% %INPUT_FILE%

echo Removing temporary drive ...
popd
echo. & pause

Now the only thing that remains is to figure out how to tell n to "Terminate batch job?" once I hit Ctrl-C and stop the program ...


Thanks to the comment by @Compo, I realized the biggest problem was actually trying to call a "subshell" - and the only reason I did that, was because I thought it was necessary to change the window title ( as per Size batch windows and set in specific location )

However, it is possible to use title command, as suggested by @Compo - and with that, there is no real need to call start; and thereby here is a BATCH file that (nearly) does everything I wanted it to:

@echo off

set "LOOPER_EXE=C:\tmp\test_bat\text_looper.exe"
set "INPUT_FILE=%~1"
for /F %%i in ("%LOOPER_EXE%") do set "EXE_BASENAME=%%~ni"

set "WINDOW_TITLE=call: %EXE_BASENAME%"
title %WINDOW_TITLE%

echo.
echo Creating temporary drive with this .bat file location, and changing current working dir there ...
pushd %~dp0
echo.
echo CWD IS %cd% ; (path to batch dir) is %~dp0
echo.
echo Program: %EXE_BASENAME% (%LOOPER_EXE%) ..
echo Using %INPUT_FILE% as input for program ..
echo.

%LOOPER_EXE% %INPUT_FILE%

echo Removing temporary drive ...
popd
echo. & pause

I say (nearly) because:

  • Even if the window title per se is changed, it still does not allow manual placement and sizing of the window, so that Windows remembers it for next time the BATCH file is started (given "Let system position window" is off) - probably related to Change BATCH file cmd window name (not just title)?
  • When I hit Ctrl-C, first I'm asked "Terminate batch job (Y/N)?", and I have to answer n, so the unmounting of the temp drive happens (else it sticks around in Windows until one does right-click and "Disconnect" on it).

Would love to know what needs to be done, to get a script that works also without these glitches ...

sdbbs
  • 4,270
  • 5
  • 32
  • 87