Strange redirect in Batch/Powershell polyglot - windows

While attempting to rewrite a batch file, I was looking into the possibility of using powershell. The main issue that I had with it was that .ps1 files aren't executable by default. I found a solution here, which I'll copy below, but I'm very confused about the syntax on the first line:
<# : batch script
#echo off
setlocal
cd %~dp0
powershell -executionpolicy remotesigned -Command "Invoke-Expression $([System.IO.File]::ReadAllText('%~f0'))"
endlocal
goto:eof
#>
# here write your powershell commands...
Here's what I know from testing:
The space between <# and : is required, though there can be multiple.
It can also have a number 0-9 beforehand, such as 3<# :, which makes me suspect that there's some weirdness involving redirects and labels.
The text after the colon is ignored and not necessary
If I add a command before it, such as git <# : batch script, the shell shows that it tried to evaluate git : batch script 0<# and can't find the file specified
Changing the # to a valid filename makes the complaint go away (naturally)
Attempting to run the line in an interactive session actually causes it to complain about not being able to find the file specified, but running it from a script (even if it's the only line in the .cmd file) silently succeeds
That last bullet is the part I'm trying to figure out. What's different about the environments? My understanding was that part of the issue was that running a bat file is parsed as if each line were manually entered. Why doesn't it complain about # being an invalid file when called via a script?

"...running a bat file is parsed as if each line were manually entered."
No, parser rules are sometimes different between command line and batch file and in this case the difference is critical.
It complains about # being an invalid file in command line, and not in batch file, because the <# : construct is (to the batch parser) just a redirected label. The same kind of label you use in goto :label or call :label.
That means that the redirection, that is not executed in batch file as the "command" associated to the redirection is a label, is executed in command line as you can not have a label in a command line.

The trick is that you need a valid construct for both, powershell and also batch.
For powershell only th <# is important to open a multiline comment.
For batch you need something that will be valid code but is invisible inspite of the echo on state.
Therefore a colon is perfect in this situation, as label lines will not be shown, even with echo on.
You can see the result of the parser, when you replace the colon with REM.
<# REM Batch script
Output:
c:\Temp>REM batch script 0<#
The # is a valid filename, but the file itself is never accesed.

Related

Windows cmd START batch file in new console with quoted arguments

Note: A solution is posted below.
Problem:
When required to start a batch file (cmd script) (i.e. CALLEE.CMD) in a new console window, from within another batch file (i.e. CALLER.CMD) , the obvious choice would be to use the internal START command: START "" "%~DP0CALLEE.CMD".
However, if we need to pass quoted parameters to CALLEE.CMD, such as START "" "%~DP0CALLEE.CMD" "ARGUMENT_1", you get an error such as 'CALLEE.CMD" "ARGUMENT_1' is not recognized as an internal or external command,
operable program or batch file.
At first glance, it's not obvious why this command should fail, as the requirements for calling the START command seem to be met. This question is posted because the reason for failure is not immediately apparent, and others may find the reference useful.
So, why is this command failing, and how to resolve the issue?
What is happening here, is that when the START command detects the command to be invoked is either an internal command (internal to cmd.exe) or a batch file, it will invoke cmd.exe to handle the command (cmd.exe /K ... will be used). The additional arguments passed to START will be translated and passed to cmd.exe. When this happens, the rules change: cmd.exe expects the command and arguments to be quoted differently from START, so this has to be taken into account when writing the original command.
The quoting for cmd.exe is documented elsewhere, such as:
correct quoting for cmd.exe for multiple arguments
http://ss64.com/nt/cmd.html.
So, to start a batch file, with quoted arguments in a new console window, we could do as follows:
START cmd.exe /K ""%~DP0CALLEE.CMD" "ARGUMENT_1" "ARGUMENT_2""
Of course, this problem only occurs if you need to quote the arguments, either because a called program expects it or if there are spaces in the argument.
Also, as a plus, if you don't require the new console window to remain open after finishing the batch file, you can substitute the switch /K with /C.
The requirement for always needing to provide a window title when calling a quoted command is avoided here, but if you do require to prefix the title of the new console window, you can do as follows:
START "<Title>" cmd.exe /K ""%~DP0CALLEE.CMD" "ARGUMENT_1" "ARGUMENT_2""
Example code:
CALLEE.CMD
#ECHO %*
#PAUSE
CALLER.CMD
#START "<Title>" cmd.exe /C ""%~DP0CALLEE.CMD" "ARGUMENT_1" ARGUMENT_2 "ARGUMENT_3""

Comprehensive list - Running code in batch file vs. entering code directly into console

I am looking for a comprehensive list of differences between running the exact same code in the windows cmd windows (entering it manually, line by line) vs. writing a batch file and running that.
I cannot possibly be the fist person to ask this, but neither Google nor multiple stack overflow searches returned what I wanted. I mostly get comparison between .bat & .cmd like this one, or specific questions about a single issue. I know there are other differences, for example i just found out that
set "filename=foo"
set "optional_postfix="
set "filetype=.cpp"
set completename=%filename%%optional_postfix%%filetype%
echo %completename%
PAUSE
behave differently.
I would like to read up on all of the differences since finding out about them after trying something for half an hour that should work and finding out about the difference afterwards is pretty annoying.
If such a list already exists here, please do not downvote me, I actually spent time looking for it and - at least for me - it was not obvious how to find it. Others might have the same issue, so please just mark it as duplicate.
There are numerous differences whether commands are executed in cmd or in batch file context, all of which refer to cmd-internal commands or features though.
The following commands that do nothing when executed in cmd context (they do not produce any output and do not affect ErrorLevel):
goto
shift
setlocal (to en-/disable command extensions or delayed expansion in cmd, invoke the instance using cmd /E:{ON|OFF} /V:{ON|OFF} instead)
endlocal
Labels (like :Label) cannot be used in cmd, they are simply ignored. Therefore call :Label cannot be used too (if you do, an error message appears and ErrorLevel becomes set to 1).
There is of course no argument expansion in cmd since there is no possibility to provide arguments. Hence a string like %1 is just returned as is ( try with echo %1). This allows to define variable names in cmd that begin with a numerical digit, which is not possible in batch files (actually you can define them, but you cannot %-expand them; try this in a batch file: set "123=abc", then set 1; you will get the output 123=abc; but trying to do echo %123% will result in the output 23, because %1 is recognised as argument reference (given that no argument is supplied)).
Undefined environment variables are not expanded (replaced) in cmd but they are maintained literally (do set "VAR=" and echo %VAR%, so you get %VAR%). In batch files however, they are expanded to empty strings instead. Note that in batch files but not in cmd, two consecutive percent signs %% are replaced by a single one %. Also in batch files but not in cmd, a single % sign becomes removed.
More information about the percent expansion (both argument references and environment variables) can be found in this thread: How does the Windows Command Interpreter (CMD.EXE) parse scripts?
The set /A command behaves differently: in cmd it returns the (last) result of an expression on the console (without trailing line-break), but in batch files it does not return anything.
The for command requires its loop variable references to be preceeded by two percent signs (like for %%I in ...) in batch files, but only a single one (like for %I in ...) in cmd. This is a consequence of the different percent expansion handling in batch files and in cmd.
Finally, some commands do not reset the ErrorLevel upon success, unless they are used within a batch file with .cmd extension. These are:
assoc
dpath
ftype
path
prompt
set
More information about resetting the ErrorLevel can be found in this post: Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?

Batch file: How to redirect output from .exe with parameter?

I am trying to write a batch file to run a .exe with a parameter and that gives and output in .csv
I wrote:
start "" "C:\Users\Me\Desktop\AnalysisSoftware\Video.exe" S1.avi>S1.csv
This command is working but the created .csv file is empty. What's wrong?
I also tried with ^ like that:
start "" "C:\Users\Me\Desktop\AnalysisSoftware\Video.exe" S1.avi^>S1.csv
Not working too...
Thank you
Cec
The start command starts the command in another process and so the output is not captured, instead you are capturing the output of the start command, which is nothing.
What you need is
start "" "cmd /c C:\Users\Me\Desktop\AnalysisSoftware\Video.exe S1.avi>S1.csv"
The distinction here is that the redirection operator is within the quotes. In your example above the redirection operator was outside the quotes and so it captured the output of the start command instead of the Video.exe. Note you also need to use cmd /c at the beginning. This is because you need a shell in order to redirect the output of the Video.exe. The /c argument tells cmd to exit as soon as the command finishes executing.

can I run vbscript commands directly from the command line (i.e. without a vbs file)?

In Python you are not obliged to use a file, you can specify -c "..." and gives the Python commands to the Python interpreter via a string on the command line.
Can I achieve the same result with vbscript?
I've seen solutions that need you to use a batch script,
But what if I am on a system with zero writing permissions?
Following the answer from #Syberdoor, I can run this:
mshta vbscript:Execute("dim result:result=InputBox(""message"",""title"",""input"")(window.close):echo result")
but it still doesn't print the result in the console.
There is one trick you can possibly use and that is mshta.exe.
You can execute code like this:
mshta vbscript:Execute("<your code here, delimit lines with : (colon)>:close")
This is of course a fantasticly insane hack and on a system where you are not even allowed to create a file I am not sure if mshta.exe would be allowed.
Maybe you can also find additional inspiration from this thread (the mshta solution is also posted there). Although mostly batch related it is imo a great compendium of several really crazy ways to fool windows into executing vbs code.
No, the interpreters shipped with Windows (wscript.exe and cscript.exe) don't support that. If you can't create a file anywhere you're out of luck. You need some kind of wrapper script to transform the argument into something that VBScript interpreters can execute.
The naïve approach would be to create a VBScript with an ExecuteGlobal statement like this:
With WScript.Arguments.Named
If .Exists("c") Then ExecuteGlobal .Item("c")
End With
However, that won't work correctly, because double quotes aren't preserved in the arguments. If you ran the above script like this:
C:\> vbsrunner.vbs /c:"WScript.Echo "foo""
it would effectively execute the statement WScript.Echo foo instead of WScript.Echo "foo", and I was unable to find a way to escape nested double quotes so that they're preserved in the argument.
What will work is a batch script that writes the argument to a temporary file, and then executes that file with a VBScript interpreter:
#echo off
setlocal
set "tempfile=%TEMP%\%RANDOM%.vbs"
>"%tempfile%" echo.%~1
cscript.exe //NoLogo "%tempfile%"
del /q "%tempfile%"
That way you can run VBScript statements on the command line like this:
C:\> vbsrunner.cmd "WScript.Echo "foo" : WScript.Echo "bar""
foo
bar
If you wanted to replicate Python's interactive mode you could use my vbsh, but that would still require the ability to create a file somewhere.

Call to .cmd file causes exit from parent .cmd file

I have a .cmd file which calls another .cmd file, as follows
parent.cmd
call "C:\Program Files\Prog1\bin\dostuff.cmd" -abc="def"
After def.cmd has run, the dos window skips to the next line, showing the prompt >
The parent.cmd file has therefore completed execution, according to the command prompt. However, after the call to dostuff.cmd, the parent.cmd file is not complete and has a number of other commands to run.
dostuff.cmd sets a number of environment variables and aliases which are required for the remaining commands in parent.cmd. Therefore it is necessary that dostuff.cmd runs in the same command prompt as parent.cmd.
dostuff.cmd is written by someone else and does all sorts of things which I know nothing of. If I call some other .cmd file of my own devising in the way described above, it executes correctly and then the parent.cmd file continues executing afterwards without any problem.
Therefore something in dostuff.cmd is shutting off the processing of parent.cmd. Any ideas what this could be and how I could stop it/get around it?
Here is the parent.cmd program:
#echo off
:Begin
echo.hello
call "C:\Program Files\Prog1\bin\dostuff.cmd" -abc="def"
echo.goodbye
:End
The output is
C:\Users\cowman\desktop>.\parent.cmd
hello
dostuff.cmd text...blah blah
C:\Users\cowman\desktop>
As you can see, the echo.goodbye code is not called.
Without a listing of dostuff.cmd, we're guessing.
My patented guess would be that dostuff.cmd itself invokes cmd. If you were to respond exit to the second prompt, you should then return to the caller (unless dostuff.cmd again invokes cmd.
if the exit response terminates the cmd session and closes the window, then that's a real mystery.
This sounds that doStuff.cmd is exited by a syntax error in the batch file while redirecting stream 2 to nul.
A syntax error stops immediatly all batch instances/call stack, but the command window stays open.
I suppose the name of your batch file isn't doStuff.cmd and also the parameters are more complex than -abc="def".
As you said in the comments, the command works from the command line, but not from your parent batch, I suppose the parameters contains percent signs or carets.
If they contain percent signs, try to quadruple them.
doStuff.cmd "printf("%d",1)"
convert it to
call doStuff.cmd "printf("%%%%d",1)"
When there are carets involved try something like
set "myCaret=^"
call doStuff.cmd "a caret%%myCaret%%"
Sounds like dostuff.cmd is exiting for some reason, which will cause the whole cmd.exe to exit.
Does it have a "exit" command anywhere?, can try changing to "exit /B" to just exit the script not cmd.exe

Resources