Page 1 of 3

Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 23 Nov 2014 14:05
by Aacini
What this strange title means? Well, this point requires a small explanation...

In StackOverflow site there are a few posts that requests a method to "execute a Batch file with an extension different than .BAT or .CMD". After some tests, I posted an answer that uses this trick:

Code: Select all

cmd.exe < NotBatch.txt
The NotBatch.txt file is executed in the command-line context instead of in the Batch-file context, so there are several differences when compared vs. a standard Batch file. This is a summary of the features/limitations of a text file executed this way.
  • Batch-file parameters %1 %2 ... can not be used nor SHIFT command.
  • @ECHO OFF command just supresses the echoing of the prompt and FOR iterations. To supress the echoing of all commands, the NotBatch.txt file must be executed this way: "cmd.exe < NotBatch.txt > NUL" and then use "echo Text > CON" in order to display text in the screen.
  • SETLOCAL/ENDLOCAL commands have not effect in the NotBatch.txt file. You may use the CALL trick to expand changing variables, or enable delayed expansion via /V:ON switch of cmd.exe. Anyway, there is an implicit SETLOCAL because cmd.exe is executed as a child process.
  • SET /P command does not read data from the keyboard, but from the lines placed after it in the NotBatch.txt file itself. This point allows to simulate the "here doc" Unix feature. To read data from keyboard, execute: "set /P var=Prompt: < CON > CON".
  • FOR and IF commands may be combined/nested at any level and are executed correctly; just use single percent characters in FOR replaceable parameters.
  • GOTO and CALL commands does not work on labels. However, an external Batch file may be invoked with or without the CALL command with the same result, even inside IF or FOR.
  • EXIT or EXIT /B commands terminate the execution of the NotBatch.txt file.
This is an EXAMPLE.TXT file:

Code: Select all

@echo off

rem Read the next four lines; "next" means placed after the FOR command
rem (this may be used to simulate a Unix "here doc")

for /L %i in (1,1,4) do (
   set /P line[%i]=
)
Line one of immediate data
This is second line
The third one
And the fourth and last one...

(
echo Show the elements of the array read:
echo/
for /L %i in (1,1,4) do call echo Line %i- %line[%i]%
) > CON
Execute it this way: cmd < EXAMPLE.TXT > NUL

You may review more extensive examples at the link given above

=======================================================================

Now the interesting part. When GOTO or CALL commands are executed in a Batch file, cmd.exe change the File Pointer (FP) used to read the next line from the running Batch file. In the case of CALL, the previous FP is stored in a return stack, so the return command (EXIT /B or GOTO :EOF) just pop the last stored value and change the FP to it.

In order to "execute" (or "simulate" or whichever) these commands in our running NotBatch.txt file, that is redirected to cmd.exe, we would need a way to modify the File Pointer of the file currently redirected to Stdin. Hey, wait... We HAVE such a program! This is precisely the purpose of my FilePointer.exe auxiliary program ! :shock:
FilePointer.exe documentation wrote: Get or set the file pointer position of a redirected file handle.
I started with a simple test as a proof of concept. I wrote the following TEST.TXT file:

Code: Select all

@echo off

echo Execute the goto > CON
FilePointer 0 %FP%

echo This should not appear! > CON

:label
echo It works! > CON
Then, I got the offset of the :label with FINDSTR /O "^" TEST.TXT that was 102, set FP=102, executed the text file via "CMD < TEST.TXT > NUL" AND IT WORKED!!! :D :D :D

After that, I developed a series of aids that allows to execute/simulate/whichever these commands in a NotBatch.txt text file in a way as standard as possible. The offsets of all labels in the NotBatch.txt file are obtained via a findstr /O "^:.*" ^< NotBatch.txt command and stored in variabes with same names, so "GOTO/CALL" could directly use those names in a FilePointer 0 %labelName% command. At beginning, I used a label placed after the "CALL" in order to have the destination point for the corresponding "RETURN", but then I realized that I could define Batch files with names like GOTO.BAT or CALL.BAT and execute they from the NotBatch.txt file without including the Batch "call" command itself. This points not only allows me to write much clearer equivalents, but also to get the return FP in CALL.BAT, so the return labels were no needed anymore. I ended with the definition of ECHO.BAT and SET.BAT that just includes the commonly used redirections. The result is very good!

This is NotBatch.txt:

Code: Select all

@echo off

rem NotBatch.txt: Simulate GOTO/CALL commands in a text file redirected to cmd.exe
rem Antonio Perez Ayala

rem Requires FilePointer.exe auxiliary program; download it from:
rem http://www.dostips.com/forum/viewtopic.php?f=3&t=5552&p=34051#p34051


rem Get the offsets of the labels in this NotBatch.txt file
for /F "tokens=1,2 delims=:" %a in ('findstr /O "^:.*" ^< NotBatch.txt') do set "%b=%a"

echo.bat
echo.bat Calling subroutine from first point
call.bat :subroutine "First time"
echo.bat Returned from subroutine
echo.bat

set.bat /P "line=Enter a few words: "
set i=0
:nextWord
   set /A i+=1
   for /F "tokens=1*" %a in ("%line%") do (
      echo.bat Word %i%- %a
      set "line=%b"
   )
if defined line goto.bat nextWord
echo/> CON

echo.bat Calling subroutine from second point
call.bat :subroutine "Second time"
echo.bat Returned from subroutine

rem Use exit /B to terminate the main program
exit /B


:subroutine
echo.bat --^^^> Greetings from the subroutine; my parameters are: %params%
echo.bat --^^^> Returning to caller program
rem Use goto.bat :EOF to return from a subroutine
rem (if you use exit /B here, the main program is terminated)
goto.bat :EOF
And these are its auxiliary Batch files.

CALL.BAT

Code: Select all

rem Support code for "CALL command" in a text file redirected to cmd.exe

rem Get and store the return (current) offset
FilePointer 0 0 /C
set EOF=%errorlevel%

rem Set the offset to jump to the :subroutine
setlocal EnableDelayedExpansion
set "sub=%1"
FilePointer 0 !%sub:~1%!

rem Get and store the parameters
set params=%*
for /F "delims=" %%a in ("!params:*%1=!") do endlocal & set params=%%a
GOTO.BAT

Code: Select all

rem Support code for "GOTO command" in a text file redirected to cmd.exe

if %1 equ :EOF (
   rem Jump to last saved return offset
   FilePointer 0 %EOF%
) else (
   rem Jump to a normal label
   setlocal EnableDelayedExpansion
   FilePointer 0 !%1!
)
SET.BAT

Code: Select all

<CON >CON set %*
ECHO.BAT

Code: Select all

>CON echo(%*
Download previous files (and FilePointer.exe one, of course) and execute they this way:

Code: Select all

cmd < NotBatch.txt > NUL
In which cases this feature may be useful? I HAVE NO IDEA! But all this stuff is EXTREMELY INTERESTING!!! 8) :mrgreen:

Antonio

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 23 Nov 2014 18:05
by dbenham
I can't imagine I will ever use this, but very interesting indeed :D

I tried using a DOSKEY macro, pretty much knowing it wouldn't work. Of course - it did not work. :(


Dave Benham

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 02 Feb 2015 07:59
by jeb
@Aacini
It's an amazing technic :D

But, do you know any way to move the file pointer back with a pure batch solution or at least a hybrid solution?
Just reading your post SO: What are the undocumented features and limitations of the Windows FINDSTR command?

I played with MORE, FIND and SORT, they all set the file pointer back to null but then they consume the complete file and
when they return the file pointer is always at the end.

I can't find any way to stop them.

jeb

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 02 Feb 2015 21:12
by Aacini
I think this is not possible, jeb. The WSH have not a method to move the file pointer of a text stream, but even if such method would exist, it is possible that it just moves the file pointer that belongs to cscript.exe, not to the parent cmd.exe!

I need to check if PowerShell have such a commandlet...

Antonio

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 11 Feb 2015 22:28
by Samir
Very cool stuff! Very handy for when the OS is locked down and won't allow .bat file execution.

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 24 Oct 2015 22:16
by lmstearn
Hey Aacini, this is all pretty cool stuff. :)
Running a (not pure) C Program aiming to get a batch script running from the "System" command. The available documentation on it is fairly scarce, and most comments on its use are either negative or relegated to the use of single statement strings.
Noticed your comment on the use of the ampersand & regarding Process.Start. Here's the problem string:

Code: Select all

system ("@echo on & cd\\ & pushd C:\\Windows\\Temp & pause >nul & set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & set VALUE_NAME=Userinit & pause >nul & FOR /F \"usebackq skip=2 tokens=1-4\" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO&(& set ValueName=%%A & set ValueType=%%B & set curregValue=%%C %%D &)&echo \"Assembles here\" & popd & pause >nul");

Leaving out the FOR statement and the associated loop bracket, it echoes as coded. Just wondering if any other characters there stand out as problematic to you?
Edit: There's something it doesn't like about the brackets (and spaces?). For example the following fails:

Code: Select all

system ("@echo on & cd\\ & pushd C:\\Windows\\Temp & pause >nul & set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & set VALUE_NAME=Userinit & pause >nul & IF DEFINED VALUE_NAME & ( & echo \"VALUE_NAME fine\" & pause >nul & ) & echo \"Assembles here\" & popd & pause >nul");

Whereas this works:

Code: Select all

system ("@echo on & cd\\ & pushd C:\\Windows\\Temp & pause >nul & set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & set VALUE_NAME=Userinit & pause >nul & IF DEFINED VALUE_NAME echo \"VALUE_NAME fine\" & pause >nul & echo \"Assembles here\" & popd & pause >nul");

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 25 Oct 2015 05:40
by penpen
lmstearn wrote:Noticed your comment on the use of the ampersand & regarding Process.Start.
I think you refer to this comment:
Aacini wrote:This example don't works as shown; it requires to change all \n newline characters by an ampersand: &
The comment is only true for the given SO example.
It is no common statement (and never was meant this way):
An ampersand ('&') is only needed between statements.

A "compound statement" (aka "block") itself is a statement, but not the leading "left parenthisis" ('(') or the trailing "right parenthisis" (')').
Using an "ampersand" after a "left parenthisis" or in front of a "right parenthisis" causes a syntax error.

Example:

Code: Select all

echo a
(
   echo b
   echo c
)
echo d


Converted this to a single line:

Code: Select all

echo a&(echo b&echo c)&echo d


Sidenote:
If you use the "set" command within chains you better enclose the variable assignment within doublequotes ('"') to avoid a trailing space character to be a part of the variable:

Code: Select all

set A=B & set "B=B"
if "%A%" == "%B%" (echo "%A%" equals "%B%".) else echo "%A%" and "%B%" are different.


penpen

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 25 Oct 2015 05:53
by lmstearn
Thanks penpen, removed the "&" and the brackets are working now. :)

Code: Select all

system ("@echo on & cd\\ & pushd C:\\Windows\\Temp & pause >nul & set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & set VALUE_NAME=Userinit & pause >nul & IF DEFINED VALUE_NAME (echo \"VALUE_NAME fine\" & pause >nul) & echo \"Assembles here\" & popd & pause >nul");

However, the original FOR statement is still problematic. :(

Code: Select all

system ("@echo on & cd\\ & pushd C:\\Windows\\Temp & pause >nul & set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & set VALUE_NAME=Userinit & pause >nul & FOR /F \"usebackq skip=2 tokens=1-4\" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (set curregValue=%%C %%D) & echo \"Assembles here\" & popd & pause >nul");

Getting the original (unescaped) code to assemble worked by removing the "&" before the "FOR":

Code: Select all

@echo on & cd\ & pushd C:\Windows\Temp & set KEY_NAME="HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" & set VALUE_NAME=Userinit & pause >nul
FOR /F "usebackq skip=2 tokens=1-4" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO set curregValue=%%C %%D & echo "Assembles here" & popd & pause >nul

Removing the ampersand before the FOR in the C code also worked. Great! :D

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 08:33
by lmstearn
Come across another little snag: the SET function appears broken thus:

Code: Select all

system ("@echo on & CALL SET A = \"VAL\" & CALL echo %A% & SETLOCAL EnableDelayedExpansion & (IF ERRORLEVEL 1 ECHO Unable to enable DelayedExpansion) & (IF DEFINED A (ECHO A IS defined) ELSE (ECHO A is NOT defined)) & ENDLOCAL & pause >nul");

Displays %A% and A is not defined. Any idea why?

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 08:43
by Squashman
%A% is not the same as %A %.

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 08:58
by lmstearn
And I have had a very long day. :oops: Thanks. :)
But why is VALUE_NAME defined here but echoes as %VALUE_NAME%?

Code: Select all

system ("@echo on & SETLOCAL EnableDelayedExpansion & cd\\ & pushd C:\\Windows\\Temp & call set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & call SET VALUE_NAME=\"Userinit\" & (IF DEFINED VALUE_NAME (ECHO IS defined) ELSE (ECHO NOT defined)) & echo %VALUE_NAME% & pause >nul");

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 09:03
by josephf
lmstearn wrote:However, the original FOR statement is still problematic. :(

Code: Select all

system ("@echo on & cd\\ & pushd C:\\Windows\\Temp & pause >nul & set KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & set VALUE_NAME=Userinit & pause >nul & FOR /F \"usebackq skip=2 tokens=1-4\" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (set curregValue=%%C %%D) & echo \"Assembles here\" & popd & pause >nul");



Sorry for reviving this thread, but to get your for loop to work you should use single %'s not %%A. Using the system command is not a batch file and you only use %% in batch file for loops.

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 09:51
by lmstearn
The System() command throws up too many hurdles. Not able to source any decent reference on it, I'll start a new thread outlining the alternative(s). Thanks. :)

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 10:45
by Aacini
You missed several important points that are described with detail in the first post of this thread. These are the ones that apply to your problem:

Aacini wrote:The NotBatch.txt file is executed in the command-line context instead of in the Batch-file context, so there are several differences when compared vs. a standard Batch file. This is a summary of the features/limitations of a text file executed this way.

  • SETLOCAL/ENDLOCAL commands have not effect in the NotBatch.txt file. You may use the CALL trick to expand changing variables, or enable delayed expansion via /V:ON switch of cmd.exe.
  • FOR and IF commands may be combined/nested at any level and are executed correctly; just use single percent characters in FOR replaceable parameters.


Antonio

Re: Executing GOTO/CALL in a cmd.exe < NotBatch.txt file!

Posted: 26 Oct 2015 20:14
by lmstearn
Yeah thanks AA- I did read it, just experimenting (as you do). But now the problem is with this code:

Code: Select all

system ("@echo on & SETLOCAL EnableDelayedExpansion & cd\\ & pushd C:\\Windows\\Temp & CALL SET KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & CALL SET VALUE_NAME=\"Userinit\" (CALL FOR /F \"USEBACKQ SKIP=2 TOKENS=1-4\" %A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>NUL`) DO (CALL SET VALUE_NAME=\"%A\" & CALL SET VALUE_TYPE=\"%B\" & CALL SET CURREGVALUE=\"%C %D\")) (IF DEFINED VALUE_NAME (>Userinitreg.txt CALL ECHO %CURREGVALUE% & PAUSE >NUL (IF '%errorlevel%' NEQ '0' (CALL ECHO Copy reg record failed. & PAUSE >NUL))) ELSE (CALL ECHO \"%KEY_NAME:\"=%\\%VALUE_NAME%\" not found. & PAUSE >NUL)) & POPD & PAUSE >NUL");

Userinitreg.txt is created as an empty file, but there is another file in the same directory created by this program with the name: "NUL)))" containing the text "Press any key to continue . . . ". Can you see where I have gone wrong?
Edit: There's something wrong with the FOR as this whittled down version will pause, but not display any text on screen:
system ("@echo on & SETLOCAL EnableDelayedExpansion & cd\\ & pushd C:\\Windows\\Temp & CALL SET KEY_NAME=\"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\" & CALL SET VALUE_NAME=\"Userinit\" (CALL FOR /F \"USEBACKQ SKIP=2 TOKENS=1-4\" %A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>NUL`) DO (CALL SET VALUE_NAME=\"%A\" & CALL SET VALUE_TYPE=\"%B\" & CALL SET CURREGVALUE=\"%C %D\")) CALL ECHO HELLO & PAUSE >NUL");

Edit2: Works if & precedes the CALL ECHO HELLO, and if & precedes the FOR /F block. Getting it now. :)

FWIW nested IFs require the outer IF to have an ELSE block otherwise no_workies.