The Windows Command Processor cmd.exe
features a built-in pseudo-variable called CmdCmdLine
that holds the original command line that invoked it.
Given that the Command Prompt is opened just by cmd.exe
(without any arguments!), CmdCmdLine
holds the (quoted) value of ComSpec
, usually "C:\Windows\system32\cmd.exe"
.
When a batch file is run from such a Command Prompt window by typing its path/name, the value of CmdCmdLine
does not change (because the already open cmd.exe
instance remains the same).
However, when the script is involved in a pipe, which initiates new cmd.exe
instances for either side, CmdCmdLine
changes to something like C:\Windows\system32\cmd.exe /S /D /c" …"
, where …
stands for the batch file together with its arguments just as provided.
We can now make use of that and check CmdCmdLine
whether it just contains a single item (the ComSpec
value) or multiple ones (ComSpec
plus some arguments), and if the second one equals /S
.
Here is an sample script (named program.bat
) to demonstrate what I mean (although this will unfortunately fail when the original Command Prompt window has been invoked by something like %ComSpec% /S …
):
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Enable delayed expansion to safely return `CmdCmdLine`:
setlocal EnableDelayedExpansion
>&2 setlocal DisableDelayedExpansion
>&2 echo/
>&2 echo BatFile = ^<%0^>
>&2 echo BatArgs = ^<%*^>
>&2 endlocal
>&2 echo/
>&2 echo ComSpec = ^<!ComSpec!^>
>&2 echo CmdLine = ^<!CmdCmdLine!^>
for %%L in (!CmdCmdLine!) do (
rem /* `!!` are consumed by delayed expansion, hence the following condition
rem is only true for the first iteration because of the `endlocal`: */
if "!!"=="" (
endlocal
) else (
rem /* This point is only reached when there are more than one items in
rem `CmdCmdLine`, which is the case when the script was involved in
rem a pipe (though it does not matter on which side it stood);
rem for this to work we assume that the second item, hence the first
rem argument, is `/S` as usual for pipes; the hosting Console Window
rem must not have been invoked by `cmd.exe /S ...` then though: */
>&2 echo CmdSwit = ^<%%~L^>
>&2 set "CmdSwit=%%~L"
if /I "%%~L"=="/S" (
>&2 echo/
>&2 set "CmdSwit="
rem /* `timeout` does not accept redirected input, which can be used
rem to distinguish between the sides of the pipe: */
> nul 2>&1 timeout /T 0 || call :PIPE
)
goto :ARGS
)
)
:ARGS
>&2 echo/
rem // Parsing each argument individually is more robust as parsing `%*`:
:ARGS_LOOP
if "%~1"=="" goto :NEXT
rem // Do something with `%1` here...
>&2 echo BatArg# = ^<%1^>
shift /1
goto :ARGS_LOOP
:NEXT
>&2 (if defined CmdSwit echo/& pause)
rem // ...
endlocal
exit /B
:PIPE
setlocal DisableDelayedExpansion
rem /* The `for /F` loop awaits the whole input text before iterating!
rem `more` hangs when there are more than about 64K lines and it converts
rem tabs to spaces (a tab becomes one space here due to switch `/T1`);
rem use `findstr "^"` instead when you expect more than about 64K lines;
rem regard that line lengths are limited to about 8K characters: */
for /F delims^=^ eol^= %%I in ('more /S /T1') do (
rem // Do something with `%%I` here...
>&2 echo PipLin# = ^<%%I^>
)
endlocal
exit /B
Note that lines beginning with >&2
are nothing but debugging lines for illustration.
Here are some usage examples (debugging output):
Script invoked by command line program.bat
(no pipe, no arguments):
BatFile = <program.bat>
BatArgs = <>
ComSpec = <C:\Windows\system32\cmd.exe>
CmdLine = <"C:\Windows\system32\cmd.exe" >
Script invoked by command line program.bat arg1 arg2
(no pipe, some arguments):
BatFile = <program.bat>
BatArgs = <arg1 arg2>
ComSpec = <C:\Windows\system32\cmd.exe>
CmdLine = <"C:\Windows\system32\cmd.exe" >
BatArg# = <arg1>
BatArg# = <arg2>
Script invoked by command line (echo pip1^&echo pip2^&rem/) | program.bat
(pipe with some lines, no arguments):
BatFile = <program.bat>
BatArgs = <>
ComSpec = <C:\Windows\system32\cmd.exe>
CmdLine = <C:\Windows\system32\cmd.exe /S /D /c" program.bat">
CmdSwit = </S>
PipLin# = <pip1>
PipLin# = <pip2>
Script invoked by command line (echo pip1^&echo pip2^&rem/) | program.bat arg1 arg2
(pipe with some lines, some arguments):
BatFile = <program.bat>
BatArgs = <arg1 arg2>
ComSpec = <C:\Windows\system32\cmd.exe>
CmdLine = <C:\Windows\system32\cmd.exe /S /D /c" program.bat arg1 arg2">
CmdSwit = </S>
PipLin# = <pip1>
PipLin# = <pip2>
BatArg# = <arg1>
BatArg# = <arg2>