Reputation: 3969
I'm using the technique in the answer to this question to have a batch file output another file. The problem is that when I reach the "&" symbol, I get errors. Here's my batch file, followed by the error output:
@echo off>zip.vbs
@echo 'Get command-line arguments.>>zip.vbs
@echo Set objArgs = WScript.Arguments>>zip.vbs
@echo InputFolder = objArgs(0)>>zip.vbs
@echo ZipFile = objArgs(1)>>zip.vbs
@echo >>zip.vbs
@echo 'Create empty ZIP file.>>zip.vbs
@echo CreateObject("Scripting.FileSystemObject").CreateTextFile(ZipFile, True).Write "PK" & Chr(5) & Chr(6) & String(18, vbNullChar)>>zip.vbs
@echo >>zip.vbs
@echo Set objShell = CreateObject("Shell.Application")>>zip.vbs
@echo >>zip.vbs
@echo Set source = objShell.NameSpace(InputFolder).Items>>zip.vbs
@echo >>zip.vbs
@echo objShell.NameSpace(ZipFile).CopyHere(source)>>zip.vbs
@echo >>zip.vbs
@echo 'Required!>>zip.vbs
@echo wScript.Sleep 2000>>zip.vbs
which produces this zip2.vbs file:
'Get command-line arguments.
Set objArgs = WScript.Arguments
InputFolder = objArgs(0)
ZipFile = objArgs(1)
ECHO is off.
'Create empty ZIP file.
ECHO is off.
Set objShell = CreateObject("Shell.Application")
ECHO is off.
Set source = objShell.NameSpace(InputFolder).Items
ECHO is off.
objShell.NameSpace(ZipFile).CopyHere(source)
ECHO is off.
'Required!
wScript.Sleep 2000
And this error output:
E:\scripts>zip2
CreateObject("Scripting.FileSystemObject").CreateTextFile(ZipFile, True).Write "
PK"
'Chr' is not recognized as an internal or external command,
operable program or batch file.
'Chr' is not recognized as an internal or external command,
operable program or batch file.
'String' is not recognized as an internal or external command,
operable program or batch file.
Upvotes: 0
Views: 574
Reputation: 34989
At first, let me provide a quick and dirty correction of your code; explanations follow below:
@echo off
> "zip.vbs" echo('Get command-line arguments.
>>"zip.vbs" echo(Set objArgs = WScript.Arguments
>>"zip.vbs" echo(InputFolder = objArgs(0)
>>"zip.vbs" echo(ZipFile = objArgs(1)
>>"zip.vbs" echo(
>>"zip.vbs" echo('Create empty ZIP file.
>>"zip.vbs" echo(CreateObject("Scripting.FileSystemObject").CreateTextFile(ZipFile, True).Write "PK" ^& Chr(5) ^& Chr(6) ^& String(18, vbNullChar)
>>"zip.vbs" echo(
>>"zip.vbs" echo(Set objShell = CreateObject("Shell.Application")
>>"zip.vbs" echo(
>>"zip.vbs" echo(Set source = objShell.NameSpace(InputFolder).Items
>>"zip.vbs" echo(
>>"zip.vbs" echo(objShell.NameSpace(ZipFile).CopyHere(source)
>>"zip.vbs" echo(
>>"zip.vbs" echo('Required!
>>"zip.vbs" echo(wScript.Sleep 2000
The ampersand has a special meaning to cmd
, it is the command concatenation operator; for instance, type echo abc & echo def
to put two echo
commands in a single line.
To output a literal &
you need to escape it by the caret like ^&
. The same is true for <
, >
and |
and the ^
itself.
In case the echo
command lies within a parenthesised block of code, you also need to escape (
and )
, otherwise it is optional (so I suggest to escape them anyway).
To output an empty line use echo(
, because echo
returns the on/off state of command-echoing, like ECHO is on/off.
. There are widely spread methods like echo.
and echo/
to achieve the same, but there are situations where those behave strangely. echo(
is the only secure way (although its syntax looks quite odd, I have to admit). By the way, echo(
can be used for echoing non-empty text also.
If you switch command-echoing off by a non-redirected @echo off
globally, you do not have to precede every further command line with @
.
I recommend to put the redirection part (>>zip.vbs
) to the beginning of the line in order to avoid weird behaviour in case the echoed string ends with a single numeral, because this may be treated as the stream identifier for redirection (0
is STDIN, 1
is STDOUT, 2
is STDERR and 3
to 9
are undefined; <
is the same as 0<
and >
/>>
is the same as 1>
/1>>
). For example, echo X=2>"zip.vbs"
would display X=
on the console as STDOUT is not redirected, and write nothing to zip.vbs
as STDERR is empty here. You could avoid this by inserting a SPACE in front of the >
, but it would be echoed also. A better way is to put the entire echo
command line in between ()
, so the numeral becomes separated from >
. But the best solution should be this: >"zip.vbs" echo X=2
.
You can also avoid multiple redirections and so many file accesses when you place all the echos inside of a parenthesised block, which is redirected once only (but as described above, do not forget to escape the parentheses here now):
@echo off
> "zip.vbs" (
echo('Get command-line arguments.
echo(Set objArgs = WScript.Arguments
echo(InputFolder = objArgs^(0^)
echo(ZipFile = objArgs^(1^)
echo(
echo('Create empty ZIP file.
echo(CreateObject^("Scripting.FileSystemObject"^).CreateTextFile^(ZipFile, True^).Write "PK" ^& Chr^(5^) ^& Chr^(6^) ^& String^(18, vbNullChar^)
echo(
echo(Set objShell = CreateObject^("Shell.Application"^)
echo(
echo(Set source = objShell.NameSpace^(InputFolder^).Items
echo(
echo(objShell.NameSpace^(ZipFile^).CopyHere^(source^)
echo(
echo('Required!
echo(wScript.Sleep 2000
)
Here is a completely different approach, where no more escaping needs to be done. The VBScript script zip.vbs
is embedded in the batch file directly, after the executable batch code. For this to work you need to place exit /B
before the VBScript portion. The VBScript part is identified by being in between a pair of lines that consist of #
only (this implies that the entire batch file including the VBScript part must not contain such line on its own; alternatively each VBScript line could be preceded by a certain identifyer prefix that is removed later, but the principle is the same). With this method the VBScript lines are output literally, there is absolutely no escaping required. So here we go:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "zip.vbs" (
for /F "delims=" %%L in ('findstr /N /R "^" "%~f0"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
set "LINE=!LINE:*:=!"
if not "!LINE:#=!"=="" (
if defined FLAG (echo(!LINE!)
) else (
endlocal
if defined FLAG (set "FLAG=") else (set "FLAG=#")
setlocal EnableDelayedExpansion
)
endlocal
)
)
endlocal
exit /B
################################################################################
'Get command-line arguments.
Set objArgs = WScript.Arguments
InputFolder = objArgs(0)
ZipFile = objArgs(1)
'Create empty ZIP file.
CreateObject("Scripting.FileSystemObject").CreateTextFile(ZipFile, True).Write "PK" & Chr(5) & Chr(6) & String(18, vbNullChar)
Set objShell = CreateObject("Shell.Application")
Set source = objShell.NameSpace(InputFolder).Items
objShell.NameSpace(ZipFile).CopyHere(source)
'Required!
wScript.Sleep 2000
################################################################################
The entire batch file is read by a for /F
loop. Since this ignores empty lines, findstr
is used to precede every line with its line number and a colon to not appear empty to for /F
. This prefix is removed in the loop later. The variable FLAG
is cleared at the beginning and is toggled as soon as a line has been found that consists of #
characters only. If FLAG
is non-empty, the current line is output, otherwise not. Delayed expansion is toggled to not lose any exclamation marks in the text.
Upvotes: 2