How to receive even the strangest command line parameters?

The code below is based on the rambling Foolproof Counting of Arguments topic on DosTips and this answer by jeb:

@echo off & setLocal enableExtensions disableDelayedExpansion
(call;) %= sets errorLevel to 0 =%
:: initialise variables
set "paramC=0" & set "pFile=%tmp%\param.tmp"

:loop - the main loop
:: inc param counter and reset var storing nth param
set /a paramC+=1 & set "pN="

:: ECHO is turned on, %1 is expanded inside REM, GOTO jumps over REM,
:: and the output is redirected to param file
for %%A in (%%A) do (
    setLocal disableExtensions
    set prompt=@
    echo on
    for %%B in (%%B) do (
        @goto skip
        rem # %1 #
    ) %= for B =%
    :skip - do not re-use this label
    @echo off
    endLocal
) >"%pFile%" %= for A =%

:: count lines in param file
for /f %%A in ('
    find /c /v "" ^<"%pFile%"
') do if %%A neq 5 (
    >&2 echo(multiline parameter values not supported & goto die
) %= if =%

:: extract and trim param value
for /f "useBack skip=3 delims=" %%A in ("%pFile%") do (
    if not defined pN set "pN=%%A"
) %= for /f =%
set "pN=%pN:~7,-3%"

:: die if param value is " or "", else trim leading/trailing quotes
if defined pN (
    setLocal enableDelayedExpansion
    (call) %= OR emulation =%
    if !pN!==^" (call;)
    if !pN!=="" (call;)
    if errorLevel 1 (
        for /f delims^=^ eol^= %%A in ("!pN!") do (
            endLocal & set "pN=%%~A"
        ) %= for /f =%
    ) else (
        >&2 echo(empty parameter values (""^) not supported & goto die
    ) %= if errorLevel =%
) else (
:: no more params on cmd line
    set /a paramC-=1 & goto last
) %= if defined =%

:: die if param value contains "
if not "%pN:"=""%"=="%pN:"=%" (
    >&2 echo(quotes (^"^) in parameter values not supported & goto die
) %= if =%

:: assign nth param, shift params, and return to start of loop
set "param%paramC%=%pN%" & shift /1 & goto loop

:last - reached end of params
:: no param values on cmd line
if %paramC% equ 0 (
    >&2 echo(no parameter values found & goto die
) %= if =%
:: list params
set param
goto end

:die
(call) %= sets errorLevel to 1 =%
:end
:: exit with appropriate errorLevel
endLocal & goto :EOF

The following conditions will terminate the program immediately:

  • no parameters found
  • multiline parameter
  • empty parameter (""", or " is permitted for the last parameter)
  • one or more quotes (") in a parameter value

To ease these restrictions, simply comment out the relevant lines. Read the inline comments for more information. Do not attempt to turn off the multiline parameter trap!


I don't think anyone found any holes in this, except for the inability to read newlines in the parameters:

@echo off
setlocal enableDelayedExpansion
set argCnt=1
:getArgs
>"%temp%\getArg.txt" <"%temp%\getArg.txt" (
  setlocal disableExtensions
  set prompt=#
  echo on
  for %%a in (%%a) do rem . %1.
  echo off
  endlocal
  set /p "arg%argCnt%="
  set /p "arg%argCnt%="
  set "arg%argCnt%=!arg%argCnt%:~7,-2!"
  if defined arg%argCnt% (
    set /a argCnt+=1
    shift /1
    goto :getArgs
  ) else set /a argCnt-=1
)
del "%temp%\getArg.txt"
set arg

The above comes from a lively DosTips discussion - http://www.dostips.com/forum/viewtopic.php?p=13002#p13002. DosTips user Liviu came up with the critical SETLOCAL DisableExtensions piece.


I invented the syntax-error-technic to solve the problem (partially).

With this solution it's even possible to receive multiline parameters and also carriage return characters.
There is no known parameter which fails!

BUT the drawback of this solution, the main process exits and only a child process continues.
That is a consequence of the capture trick, a syntax error is created by using an invalid parenthesis block ( Prepare ) PARAMS....
But the syntax error itself outputs the complete block, including the expanded value of %*.
The output is redirected to a file by the permanent redirect technic.
And the child process can retrieve the complete parameter from the file.

This solution can be useful, when the batch file only handles the parameter and always exit afterwards.

@echo off
REM *** Thread redirector 
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F

REM *** Clear params.tmp
break > params.tmp

start "" /b cmd /k "%~d0\:StayAlive:\..\%~pnx0 params.tmp"

(set LF=^
%=empty=%
)
REM *** Change prompt for better recognition
prompt #PROMPT#


REM *** Change streams permanently
REM *** stream1 redirects to params.tmp
REM *** stream2 redirects to nul
echo on >nul 2>nul 0>nul 3>params.tmp 4>nul 5>&3

@REM *** This is the magic part, it forces a syntax error, the error message itself shows the expanded %asterix without ANY modification
( Prepare ) PARAMS:%LF%%*%LF%

echo Works
exit /b


REM *** Second thread to fetch and show the parameters
:StayAlive

:__WaitForParams
if %~z1 EQU 0 (
    goto :__WaitForParams
)
REM *** Show the result
findstr /n "^" %1