Take for instance the following example files a.bat and b.bat:
a.bat
#echo off
echo in a: %*
call b %*
b.bat
#echo off
echo in b: %*
and consider this invocation and output:
c:\>a 1 2 3 %% %%%%
in a: 1 2 3 %% %%%%
in b: 1 2 3 % %%
Is there a way to pass the parameters from the command line to b.bat such that it sees the same parameters that a.bat saw? Is it possible to prevent the system from processing the parameters, i.e. removing the escaping % character?
Because this looks so simple, I assume that I must have missed something, but from what you've posted, this looks like all you should need:
C:\a.cmd
#Echo In %~n0: %*
#Call "b.cmd" %%*
C:\b.cmd
#Echo In %~n0: %*
Example:
C:\>a 1 2 3 %% %%%%
In a: 1 2 3 %% %%%%
In b: 1 2 3 %% %%%%
Yes you can do that, but instead of removing a character, you need to add more escape characters.
But to solve it for any (or at least most) parameter you need to escape all problematic characters &|<>"^
This can handle even parameters like:
a.bat Bob^&Alice say: "Cat & dog"
#echo off
REM *** Fetch all arguments
goto :init :initCallBack
:initCallBack
REM *** Prepare the arguments for a secure CALL
call :prepareArgument allArgs arg
REM *** b.bat will see the same arguments like this file
call b.bat %%allArgs%%
exit /b
:init
::: Get all arguments with minimal interference
::: Only linefeeds and carriage returns can't be fetched
::: Only linefeeds can create a syntax error
::: return arg=All arguments, arg1..arg<n>=A single argument
setlocal EnableDelayedExpansion
set "argCnt="
:getArgs
>"%temp%\getArg.txt" <"%temp%\getArg.txt" (
setlocal DisableExtensions
(set prompt=#)
echo on
if defined argCnt (
for %%a in (%%a) do rem . %1.
) ELSE (
for %%a in (%%a) do rem . %*.
)
echo off
endlocal
set /p "arg%argCnt%="
set /p "arg%argCnt%="
set "arg%argCnt%=!arg%argCnt%:~7,-2!"
if defined arg%argCnt% (
if defined argCnt (
shift /1
)
set /a argCnt+=1
goto :getArgs
) else set /a argCnt-=1
)
del "%temp%\getArg.txt"
goto :initCallBack
:prepareArgument
::: Convert a string, so it can be used as an argument for calling a program
set "var=!%~2!"
if defined var (
set "var=!var:^=^^!"
set "var=!var:"=^^"!"
set "var=!var:&=^&!"
set "var=!var:|=^|!"
set "var=!var:>=^>!"
set "var=!var:<=^<!"
)
set "%~1=!var!"
exit /b
Related
Rem ask for user input
set /p installerchoice= Please enter number for installer you wish to use:
Rem check to make sure input is a number
SET "var="&for /f "delims=0123456789" %%n in ("%installerchoice%") do set "var=%%n"
if defined var Goto Choose
rem check to make sure input is not zero
if %installerchoice% equ 0 goto Choose
rem check to make sure input isn't to high
if %installerchoice% gtr %Count% goto Choose
lection=%%i
I'm trying to make sure a valid input (a numerical value between 1 and how ever many options there are) is input. The only thing that seems to cause a problem right now is if a " is entered. Is there anyway to prevent that from crashing the bat?
Here's how I'd tackle the task in your code:
:Choose
#Set "InstallerChoice="
#Set /P "InstallerChoice=Please enter the number for the installer you wish to use: "
#SetLocal EnableDelayedExpansion
#(Echo(!InstallerChoice!|"%__AppDir__%findstr.exe" /RX "[123456789][0123456789]* 0">NUL||(EndLocal&Goto Choose)) 2>NUL
#EndLocal
#Echo Your number %InstallerChoice% is valid.
#Pause
However, had you provided the rest of the code, which I assume has parsed a directory within a for loop and allocated filenames to numbers in a menu like manner, there may have been a better or different methodology.
For Single Digit input, Use Choice
CHOICE /N /C 123456789 /M "Select Installer Number 1 2 3 4 5 6 7 8 9"
GOTO :installer%errorlevel%
And use labels for your Installers such as:
:installer1
::commands
:installer2
::commands
To offer More options and Flexibility, Make Use of an Input Validation Function
#ECHO OFF
::: REM Delayed expansion is required only for testing of Second Call Parameter
::: Is not required if that testing is removed.
SETLOCAL EnableDelayedExpansion
::: REM The below 6 lines allow testing of the function, and are not part of the function.
:start
cls
CALL :Filter option number
ECHO %option%
pause
GOTO :start
:::::::::::::::::::::::::::::: Function by T3RRY
::: Using this Function-
::: When Needing User Input:
:::
::: CALL :Filter <VarName> <AllowType>
::: Where <VarName> is replaced with the Variable name to be Set
::: And <AllowType> is replaced with letter, number, or alphanumerical
:Filter
::: Assign the variable (Passed by call Argument) to be set and tested
SET "testinput=%1"
SET "permitted=%~2"
::: Test Parameter Usage is Correct- For Debugging Only.
IF NOT DEFINED testinput (
ECHO Improper use of Filter Function. CALL :Filter must include a Parameter to define the Variable to be Set
Pause
Exit
)
Set P_True=0
IF DEFINED permitted (
IF /I "%permitted%"=="number" Set /a P_True+=1
IF /I "%permitted%"=="letter" Set /a P_True+=1
IF /I "%permitted%"=="alphanumerical" Set /a P_True+=1
IF "!P_True!"=="0" (
ECHO Incorrect Parameter used for CALL :Filter 2nd Call Parameter.
ECHO Parameter may be undefined, If Defined must include "number" OR "letter" OR "alphanumerical"
Pause
Exit
)
)
::: Expand testinput to the Name of the Variable to be Assigned, Ensures Undefined
SET %testinput%=
::: Ensure Undefined Value for Input
SET input=
:Get_Input
::: Exit Filter once Acceptable Variable is Set
IF DEFINED input GOTO return
::: Create and start VBS script to Launch an Input Box and Store the input to text file for retrieval from this Batch program.
SET /P input=[Select %testinput%:]
::: Test to ensure Variable defined. Needed here to prevent environment variable not defined message ahead of next test Should the variable not be defined.
IF NOT DEFINED input GOTO invInput
::: Test for Doublequotes and reset variable if present
SET Input | FIND """" >NUL
IF NOT ERRORLEVEL 1 SET Input=
IF NOT DEFINED input GOTO invInput
:: REM Block Tilde
SET Input | FIND "~" >NUL
IF NOT ERRORLEVEL 1 SET Input=
IF NOT DEFINED input GOTO invInput
::: Test for Spaces
IF NOT "%input%"=="%input: =%" GOTO invInput
::: Test for all other standard Symbols.
::: To permit symbols REM out permitted Symbol and Call Filter without Second Parameter.
IF NOT "%input%"=="%input:&=%" GOTO invInput
IF NOT "%input%"=="%input:(=%" GOTO invInput
IF NOT "%input%"=="%input:)=%" GOTO invInput
IF NOT "%input%"=="%input:<=%" GOTO invInput
IF NOT "%input%"=="%input:>=%" GOTO invInput
IF NOT "%input%"=="%input:{=%" GOTO invInput
IF NOT "%input%"=="%input:}=%" GOTO invInput
IF NOT "%input%"=="%input:]=%" GOTO invInput
IF NOT "%input%"=="%input:[=%" GOTO invInput
IF NOT "%input%"=="%input:#=%" GOTO invInput
IF NOT "%input%"=="%input:^=%" GOTO invInput
IF NOT "%input%"=="%input:+=%" GOTO invInput
IF NOT "%input%"=="%input:-=%" GOTO invInput
IF NOT "%input%"=="%input:/=%" GOTO invInput
IF NOT "%input%"=="%input:\=%" GOTO invInput
IF NOT "%input%"=="%input:|=%" GOTO invInput
IF NOT "%input%"=="%input:$=%" GOTO invInput
IF NOT "%input%"=="%input:!=%" GOTO invInput
IF NOT "%input%"=="%input:?=%" GOTO invInput
IF NOT "%input%"=="%input:#=%" GOTO invInput
IF NOT "%input%"=="%input:'=%" GOTO invInput
IF NOT "%input%"=="%input:,=%" GOTO invInput
IF NOT "%input%"=="%input:.=%" GOTO invInput
IF NOT "%input%"=="%input:;=%" GOTO invInput
IF NOT "%input%"=="%input:`=%" GOTO invInput
:: REM Length restriction. Adjust the length (Number) or Rem out as Desired
::: IF NOT "%input:~12%"=="" GOTO invInput
IF NOT DEFINED permitted GOTO :Get_Input
IF /I "%permitted%"=="number" GOTO :P_numbers
IF /I "%permitted%"=="letter" GOTO :P_letters
IF /I "%permitted%"=="alphanumerical" GOTO :P_alphanumerical
::: REM Permit only Numbers, Letters, or Letters and Numbers. Also Blocks *,! and %
:P_numbers
echo.%input%| findstr /R "[^0-9]" >nul 2>nul
if NOT errorlevel 1 GOTO invInput
GOTO :Get_Input
:P_letters
echo.%input%| findstr /R "[^a-zA-Z]" >nul 2>nul
if NOT errorlevel 1 GOTO invInput
GOTO :Get_Input
:P_alphanumerical
echo.%input%| findstr /R "[^a-zA-Z0-9]" >nul 2>nul
if NOT errorlevel 1 GOTO invInput
GOTO :Get_Input
:invInput
SET input=
::: REM Specifies the permitted input types.
IF DEFINED permitted (
ECHO Input of %permitted% characters is allowed. Other Symbols and Characters are Not Permitted.
) else (
ECHO Invalid Character Entered. Try Again
)
GOTO :Get_Input
:return
::: assigns the input value to the variable name being validated.
SET "%testinput%=%input%"
SET input=
GOTO :EOF
The solution is quite simple: just enable delayed variable expansion before the for /F loop and use it for variable !InstallerChoice!, then any " the user enters is no longer recognised by the parser:
:Choose
set "InstallerChoice=" & rem // (reset variable to avoid keeping previous value)
set /P InstallerChoice="Please enter number for installer you wish to use: "
rem // Check to make sure input is a number:
set "var=" & setlocal EnableDelayedExpansion
for /F "eol=0 delims=0123456789" %%N in ("!InstallerChoice!") do set "var=%%N"
if defined var (endlocal & Goto Choose) else endlocal
rem // Check to make sure input is not zero:
if %InstallerChoice% equ 0 goto Choose
rem // Check to make sure input isn't to high:
if %InstallerChoice% gtr %Count% goto Choose
By the way, let me recommend to put set "InstallerChoice=" just before set /P, because when the user just presses {Enter}, the previous value is going to be kept otherwise.
And you should add eol=0 to the for /F loop options to not come into trouble when the user enters something beginning with the default eol character ;.
I have written a script that contains a function that should loop through a list and return a value given the index of the item in said list. I have a function called: :find that should take 2 arguments: the list and the item position. I am unsure of how to process the multiple parameters in the function. This script runs fine if I replace %LIST% with %MY_LIST% inside the loop and I remove the %MY_LIST% from the argument list tht is passed to call :find, but I really want to know how to pass multiple arguments. I take it that they are just passed to the function as a whole string...
#echo off
setlocal enableDelayedExpansion
cls
:: ----------------------------------------------------------
:: Variable declarations
:: ----------------------------------------------------------
set RETURN=-1
set MY_LIST=("foo" "bar" "baz")
set TARGET_INDEX=1
:: ----------------------------------------------------------
:: Main procedure
:: ----------------------------------------------------------
call :log "Finding item %TARGET_INDEX%..."
call :find %MY_LIST% %TARGET_INDEX%
call :log "The value is: %RETURN%"
goto Exit
:: ----------------------------------------------------------
:: Function declarations
:: ----------------------------------------------------------
:find
call :log "Called `:find` with params: [%*]"
set /a i=0
set LIST=%~1 & shift
for %%a in %LIST% do (
if !i! == %~1 (
set RETURN=%%a
)
set /a i=!i!+1
)
goto:EOF
:printDate
for /f "tokens=2-4 delims=/ " %%a in ('echo %DATE%') do (
set mydate=%%c/%%a/%%b)
for /f "tokens=1-3 delims=/:./ " %%a in ('echo %TIME%') do (
set mytime=%%a:%%b:%%c)
echo|set /p="[%mydate% %mytime%] "
goto:EOF
:log
call :printDate
echo %~1
goto:EOF
:: ----------------------------------------------------------
:: End of script
:: ----------------------------------------------------------
:Exit
Update
My script now works fine; thanks to nephi12. http://pastebin.com/xGdFWmnM
call :find "%MY_LIST%" %TARGET_INDEX%
goto :EOF
:find
echo %~1 %~2
goto :EOF
they are passed the same as args to the script... ;)
Here is another method to do an index lookup on a space delimited list of values. Define the list without enclosing parentheses. Single words don't need to be quoted. Phrases with space, tab, semicolon, or equal must be quoted. Also values with special characters like & and | should be quoted.
Then reverse the order of your :FIND arguments - first the index, followed by the actual list. Use SHIFT in a FOR /L loop to get the desired index value into the first argument.
One advantage of this solution is that it can support any number of values, as long as they fit within the 8191 character per line limit. The nephi12 solution is limited to 9 values.
#echo off
setlocal
set MY_LIST=foo bar baz "Hello world!"
call :find %~1 %MY_LIST%
echo return=%return%
exit /b
:find index list...
for /L %%N in (1 1 %~1) do shift /1
set "return=%~1"
exit /b
Try this, I think it answers your question. Put it in a bat file and then build whatever else you need around it after you see this work. Execute it with a quoted string from a command prompt. YourBatFile "Arg1 Arg2 Arg3 Etc"
#echo off
call :DoSomethingWithEach %~1
goto :eof
:DoSomethingWithEach
for %%a in (%*) do echo.%%a
goto :eof
How can I set result variable in a scope surrounded with parantheses ('if' or 'for'-loop). The result is correct (>> RESULT: aaa = bbb), when procedure is called directly, and fails when used in for-loop or if-statement (>> RESULT: ccc = ).
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
echo RESULT: ccc = %ccc%
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal
Thanks!!
When a compound statement enclosed in parentheses is to be executed,
the statement is first parsed from the open parenthesis all of the
way to the matching close-parenthesis.
At this time, any %var% is replaced by that var's value from the
environment AT THE TIME IT IS PARSED (ie its PARSE-TIME value.)
THEN if the statement seems valid, it is executed.
There are three common ways of accessing the RUN-TIME value of the
variable (as a FOR loop executes, for instance.)
1/ SETLOCAL ENABLEDELAYEDEXPANSION which switches to a mode where
!var! may be used to access the runtime value of var
2/ CALL set var2=%%var%% to set the value of var2 from the
runtime value of var
3/ Executing a subroutine, internal or external within which %var%
will be the runtime value.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN (1 2 3) DO (
ECHO START of run %%i
ECHO using ^!time^! : !time! - PARSE TIME was %time%
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
timeout /t 5
ECHO using ^!time^! : !time!
CALL ECHO using CALL %%%%TIME%%%% : %%TIME%%
CALL :report
ECHO END of run %%i
ECHO.
)
GOTO :eof
:report
ECHO :report says TIME is %TIME%
GOTO :eof
A few items to note:
The instruction
IF ERRORLEVEL n echo errorlevel is n OR GREATER
ALWAYS interprets the RUN-TIME value of ERRORLEVEL
IF SET VAR ALWAYS interprets the RUN-TIME value of VAR
The magic variables like ERRORLEVEL and TIME should never
be SET. If you execute
SET ERRORLEVEL=dumb
then ERRORLEVEL will adopt the value dumb because the current
value in the environment takes priority over the system-assigned value.
inside a code block (=surrounded with parantheses) you need delayed expansion and !variables!, not %variables%:
:: =====================================
#setlocal
#echo off
#rem (1)
call :testReturn aaa
echo RESULT: aaa = %aaa%
#rem (2)
if "1" == "1" (
call :testReturn ccc
setlocal enabledelayedexpansion
echo RESULT: ccc = !ccc!
endlocal
)
goto :eof
:testReturn
set %~1=bbb
exit /b
endlocal
In windows batch files, I know %1 is replaced with the first argument, %2 is replaced with the 2nd argument and %* is replaced with all arguments.
Is there a way to get all the arguments after the 1st one? (e.g. arguments 2-N) What about all the arguments after the 2nd one?
The SHIFT command doesn't seem to affect %*.
#ECHO OFF
SETLOCAL
CALL :allafter 3 %*
ECHO args=%args%
GOTO :eof
:allafter
FOR /l %%a IN (1,1,%1) DO SHIFT
(SET args=)
:argloop
shift
IF NOT .%1==. SET args=%args% %1&GOTO argloop
IF DEFINED args SET args=%args:~1%
GOTO :eof
to get everything after the 3rd argument to ARGS
Edit - to take care of space-separated elements which may include commas
#ECHO OFF
SETLOCAL
CALL :allafter 3 %*
ECHO args=%args%
CALL :allafter2 3 %*
ECHO args=%args%
GOTO :eof
:allafter
FOR /l %%a IN (1,1,%1) DO SHIFT
(SET args=)
:argloop
shift
IF NOT .%1==. SET args=%args% %~1&GOTO argloop
IF DEFINED args SET args=%args:~1%
GOTO :EOF
:allafter2
SET /a count=%1
SET args=%*
:arg2loop
SET oldargs=%args%
call SET args=%%args:*%1 =%%
IF "%args%"=="%oldargs%" (call SET args=%%args:*%1,=%%) ELSE (SET /a count-=1)
shift
IF %count% gtr -1 GOTO arg2loop
GOTO :EOF
Hmm- spoke too soon. This modified routine should play nicer. The previous version treated what was to be defined as one argument one,two,three as three separate arguments when the remove-leading-n was invoked.
Well, there is a way-ish...
By using the /n switch on the shift command, you can sort of do something like it. However, it will delete all of the argument and put them into a certain variable (so you can't call %3 anymore without a for loop).
#setlocal enableextensions
#echo off
:loop
if "%~2" equ "" goto end
set variable=%variable% %~2
shift /2
goto loop
:end
echo %1
echo %variable%
endlocal
To separate the parameters again just do a simple for loop (I'm sure you can find documentation on it somewhere).
Solution without shift & goto:
#echo off &setlocal enabledelayedexpansion
set /a count=0
for %%i in (%*) do set /a count+=1
set "args="
for /l %%i in (2,1,%count%) do if defined args (call set "args=!args! %%%%i") else call set "args=%%%%i"
echo.%args%
endlocal
> type t.bat
#echo off
echo %*
for /f "tokens=1,*delims= " %%i in ("%*") do echo %%j
> t a b c d e f,g h i
a b c d e f,g h i
b c d e f,g h i
> t a,a b c d e f,g h i
a,a b c d e f,g h i
b c d e f,g h i
>
Further to How to Pass Command Line Parameters in batch file how does one get the rest of the parameters with specifying them exactly? I don't want to use SHIFT because I don't know how many parameters there might be and would like to avoid counting them, if I can.
For example, given this batch file:
#echo off
set par1=%1
set par2=%2
set par3=%3
set therest=%???
echo the script is %0
echo Parameter 1 is %par1%
echo Parameter 2 is %par2%
echo Parameter 3 is %par3%
echo and the rest are %therest%
Running mybatch opt1 opt2 opt3 opt4 opt5 ...opt20 would yield:
the script is mybatch
Parameter 1 is opt1
Parameter 2 is opt2
Parameter 3 is opt3
and the rest are opt4 opt5 ...opt20
I know %* gives all the parameters, but I don't wan't the first three (for example).
Here's how you can do it without using SHIFT:
#echo off
for /f "tokens=1-3*" %%a in ("%*") do (
set par1=%%a
set par2=%%b
set par3=%%c
set therest=%%d
)
echo the script is %0
echo Parameter 1 is %par1%
echo Parameter 2 is %par2%
echo Parameter 3 is %par3%
echo and the rest are %therest%
#echo off
setlocal enabledelayedexpansion
set therest=;;;;;%*
set therest=!therest:;;;;;%1 %2 %3 =!
echo the script is %0
echo Parameter 1 is %1
echo Parameter 2 is %2
echo Parameter 3 is %3
echo and the rest are: %therest%
This works with quoted arguments and with arguments that have equal signs or commas, as long as first three arguments didn't have these special delimiter chars.
Sample output:
test_args.bat "1 1 1" 2 3 --a=b "x y z"
Parameter 1 is "1 1 1"
Parameter 2 is 2
Parameter 3 is 3
and the rest are: --a=b "x y z"
This works by replacing %1 %2 %3 in original command line %*. The first five semicolons are there just to make sure that only the first occurrence of these %1 %2 %3 is replaced.
The following code uses shift, but it avoids to parse the command line using for and lets the command line interpreter do this job (regard that for does not parse double-quotes properly, for instance argument set A B" "C is interpreted as 3 arguments A, B", "C by for, but as 2 arguments A, B" "C by the interpreter; this behaviour prevents quoted path arguments like "C:\Program Files\" from being handled correctly):
#echo off
set "par1=%1" & shift /1
set "par2=%1" & shift /1
set "par3=%1" & shift /1
set therest=
set delim=
:REPEAT
if "%1"=="" goto :UNTIL
set "therest=%therest%%delim%%1"
set "delim= "
shift /1
goto :REPEAT
:UNTIL
echo the script is "%0"
echo Parameter 1 is "%par1%"
echo Parameter 2 is "%par2%"
echo Parameter 3 is "%par3%"
echo and the rest are "%therest%"
rem.the additional double-quotes in the above echoes^
are intended to visualise potential whitespaces
The remaining arguments in %therest% might not look like the way they were originally concerning the delimiters (remember the command line interpreter also treats TABs, ,, ;, = as delimiters as well as all combinations), because all delimiters are replaced by a single space here. However, when passing %therest% to some other command or batch file, it will be parsed correctly.
The only limitation I encountered so far applies to arguments containing the caret character ^. Other limitations (related to <, >, |, &, ") apply to the command line interpreter itself.
#ECHO OFF
SET REST=
::# Guess you want 3rd and on.
CALL :SUBPUSH 3 %*
::# ':~1' here is merely to drop leading space.
ECHO REST=%REST:~1%
GOTO :EOF
:SUBPUSH
SET /A LAST=%1-1
SHIFT
::# Let's throw the first two away.
FOR /L %%z in (1,1,%LAST%) do (
SHIFT
)
:aloop
SET PAR=%~1
IF "x%PAR%" == "x" (
GOTO :EOF
)
ECHO PAR=%PAR%
SET REST=%REST% "%PAR%"
SHIFT
GOTO aloop
GOTO :EOF
I like to use subroutines instead of EnableDelayedExpansion. Above is extract from my dir/file pattern processing batch. Don't say this cannot handle arguments with =, but at least can do quoted path with spaces, and wildcards.
One more alternative :-)
some of the other answers did not deal with quotes, or with extra spaces between parameters. Disclaimer: I have not tested this extremely well.
This is all just a workaround for %* not being updated after a shift ugg!
I implemented it as a POP starting from the technique by #Pavel-P.
It relies on the batch parser to remove extra spaces
It depends on a global variable 'Params'
~ removes quotes -- leaving it up to you to re-quote as desired. Remove the ~ if you don't want that.
:PopFirstParam
#set varname=%~1
#set %varname%=%~2
#rem #echo %varname% = '!%varname%!'
#if '%3' equ '' set Params=&goto :eof
#call :popone %Params%
#goto :eof
:popone
#set Params=;;;%*
#set Params=!Params:;;;%1 =!
#goto :eof
Usage:
#setlocal enabledelayedexpansion
#set Params=%*
#call :PopFirstParam P1 %Params%
#call :PopFirstParam P2 %Params%
#call :PopFirstParam P3 %Params%
#call :PopFirstParam P4 %Params%
#rem etc . . .
Specifically I use it to optionally run commands asynchronously:
#setlocal enabledelayedexpansion
#rem set variables that decide what to be run . . . .
#call :RunIfDefined doNum1 "Title 1" mycmd1.exe some "params" here
#call :RunIfDefined doNum2 "MyTitle 2" mylongcmd2.exe some "params" here
#call :RunIfDefined doNum3 "Long Title 3" mycmd3.exe some "params" here
#goto :eof
:RunIfDefined
#set Params=%*
#call :PopFirstParam DefineCheck %Params%
#if not defined %DefineCheck% goto :eof
#call :PopFirstParam WindowTitle %Params%
#call :PopFirstParam Command %Params%
#rem %Params% should now be the remaining params (I don't care about spaces here)
#echo start "%WindowTitle%" "%Command%" %Params%
#start "%WindowTitle%" "%Command%" %Params%
#goto :eof
C:\test> set doNum1=yes
C:\test> set doNum3=yes
C:\test> call dothings.bat
start "Title 1" "mycmd1.exe" some "params" here
start "Long Title 3" "mycmd3.exe" some "params" here
too bad DOS doesn't have a #include