是否可能在不使用临时文件的情况下将VBScript嵌入和执行在批处理文件中?

47

长期以来,人们一直在批处理文件中嵌入和执行VBScript。但是,在我看到的所有已发表的解决方案(即在最初提出此问题时)中,都需要编写临时VBS文件。例如:在Windows批处理文件中嵌入VBScript

是否有可能在不编写临时文件的情况下执行批处理中嵌入的VBScript?


PowerShell + VBScript怎么样? --> https://dev59.com/YLzpa4cB1Zd3GeqPStjn - FreeSoftwareServers
5个回答

66
Note - 请跳转到本答案底部的2014-04-27更新部分,以获取最佳解决方案。
我曾经认为答案是否定的。但是DosTips用户Liviu发现,当<SUB>字符(Ctrl-Z,0x1A,十进制26)嵌入批处理文件时,它会产生奇怪的影响。如果在Ctrl-Z之前加上REM(或:: remark hack),则可以使后续批处理命令执行,就像行终止符一样。http://www.dostips.com/forum/viewtopic.php?p=13160#p13160 这已在XP Home Edition sp3、Vista Home Premium sp2 64位和Vista Enterprise sp2 32位上得到确认。我假设它也适用于其他Windows版本。
注意 - 下面的代码应该包含了嵌入的Ctrl-Z字符。我发誓在使用IE8浏览器查看该网站时曾经看到过它们。但是它们似乎已经从这篇帖子中消失了,我无法弄清楚如何再次发布它们。我已经用字符串<SUB>替换了这些字符。
::<sub>echo This will execute in batch, but it will fail as vbs.
rem<SUB>echo This will execute in batch, and it is also a valid vbs comment
::'<SUB>echo This will execute in batch and it is also a valid vbs comment

这是成功的批处理/vbs混合的关键。只要每个批处理命令前面都有rem<SUB>::'<SUB>,那么vbs引擎就看不到它,但批处理命令会运行。只需确保使用EXITEXIT /B终止批处理部分。然后剩余的脚本可以是正常的vbs。
如果需要,甚至可以有一个批处理标签。 :'Label既是有效的vbs注释,也是有效的批处理标签。
这是一个微不足道的混合脚本。 (再次使用<SUB>代替嵌入的Ctrl-Z字符)
::'<SUB>@cscript //nologo //e:vbscript "%~f0" & exit /b
WScript.Echo "Example of a hybrid VBS / batch file"

更新 2012-04-15

jeb找到了一个避免使用尴尬的CTRL-Z的解决方案,但它在开头打印了ECHO OFF,并设置了一些无关的变量。

我已经找到了一个没有CTRL-Z的解决方案,消除了无关的变量,并且更容易理解。

通常情况下,在批处理的REM语句之后,特殊字符如&|<>等是无效的。但特殊字符在REM.之后是有效的。我在http://www.dostips.com/forum/viewtopic.php?p=3500#p3500上发现了这个信息。测试表明,REM.仍然是一个有效的VBS注释。编辑 - 根据jeb的评论,更安全的做法是使用REM^(插入符号后面有一个空格)。

这是一个使用REM^ &的简单VBS/batch混合程序。唯一的缺点是它会在开头打印REM &,而jeb的解决方案会打印ECHO OFF
rem^ &@cscript //nologo //e:vbscript "%~f0" & exit /b
WScript.Echo "Example of a hybrid VBS / batch file"

这是另一个简单的例子,演示了包括调用标记子程序在内的多个批处理命令。
::' VBS/Batch Hybrid

::' --- Batch portion ---------
rem^ &@echo off
rem^ &call :'sub
rem^ &exit /b

:'sub
rem^ &echo begin batch
rem^ &cscript //nologo //e:vbscript "%~f0"
rem^ &echo end batch
rem^ &exit /b

'----- VBS portion ------------
wscript.echo "begin VBS"
wscript.echo "end VBS"
'wscript.quit(0)

I still like the CTRL-Z solution because it eliminates all extraneous output.
更新于2012年12月17日:Tom Lavedas在Google Groups上发布了一种方便地从批处理脚本运行动态VBS的方法:No file VBS hybrid scripting。该方法使用mshta.exe(Microsoft HTML应用程序宿主)。
他最初的批处理解决方案依赖于外部的小型VBS.BAT脚本来执行FOR / F中的VBS。我稍微修改了语法,使其方便嵌入到任何给定的批处理脚本中。
它非常慢,但非常方便。它仅限于执行单行VBS。
VBS通常是正常编写的,除了所有引号必须加倍:用于包含字符串的引号必须写为"",而用于字符串内部的引号必须写为""""。通常,在FOR / F的IN()子句中执行迷你脚本。如果已重定向或管道stdout,则可以直接执行它。
只要安装了IE,它就应该适用于从XP开始的任何Windows操作系统。
@echo off
setlocal
:: Define simple batch "macros" to implement VBS within batch
set "vbsBegin=mshta vbscript:Execute("createobject(""scripting.filesystemobject"")"
set "vbsBegin=%vbsBegin%.GetStandardStream(1).write("
set ^"vbsEnd=):close"^)"

:: Get yesterday's date
for /f %%Y in ('%vbsBegin% date-1 %vbsEnd%') do set Yesterday=%%Y
set Yesterday
pause
echo(

:: Get pi
for /f %%P in ('%vbsBegin% 4*atn(1) %vbsEnd%') do set PI=%%P
set PI
pause
echo(

set "var=name=value"
echo Before - %var%
:: Replace =
for /f "delims=" %%S in (
  '%vbsBegin% replace(""%var%"",""="","": "") %vbsEnd%'
) do set "var=%%S"
echo After  - %var%
pause
echo(

echo Extended ASCII:
for /l %%N in (0,1,255) do (

  %=  Get extended ASCII char, except can't work for 0x00, 0x0A.  =%
  %=  Quotes are only needed for 0x0D                             =%
  %=    Enclosing string quote must be coded as ""                =%
  %=    Internal string quote must be coded as """"               =%
  for /f delims^=^ eol^= %%C in (
    '%vbsBegin% """"""""+chr(%%N)+"""""""" %vbsEnd%'
  ) do set "char.%%N=%%~C"

  %=  Display result  =%
  if defined char.%%N (
    setlocal enableDelayedExpansion
    echo(   %%N: [ !char.%%N! ]
    endlocal
  ) else echo(   %%N: Doesn't work :(
)
pause
echo(

:: Executing the mini VBS script directly like the commented code below 
:: will not work because mshta fails unless stdout has been redirected
:: or piped.
::
::    %vbsBegin% ""Hello world"" %vbsEnd%
::

:: But this works because output has been piped
%vbsBegin% ""Hello world"" %vbsEnd% | findstr "^"
pause

更新于2014-04-27

DosTips网站上有一个很棒的js/vbs/html/hta混合体和奇美拉在cmd/bat中的汇编。来自不同人的许多好东西。

在那个帖子中,DosTips用户Liviu发现了一个漂亮的VBS/batch混合体解决方案,使用了WSF。

<!-- : Begin batch script
@echo off
cscript //nologo "%~f0?.wsf"
exit /b

----- Begin wsf script --->
<job><script language="VBScript">
  WScript.Echo "VBScript output called by batch"
</script></job>

我认为这个解决方案非常棒。批处理和WSF部分由漂亮的标题明确分开。批处理代码绝对正常,没有任何奇怪的语法。唯一的限制是批处理代码不能包含-->
同样,WSF中的VBS代码也是绝对正常的。唯一的限制是VBS代码不能包含</script>
唯一的风险是未记录使用"%~f0?.wsf"作为要加载的脚本。不知何故,解析器可以正确地找到和加载正在运行的.BAT脚本"%~f0",并且?.wsf后缀神秘地指示CSCRIPT将脚本解释为WSF。希望微软永远不会禁用那个“功能”。
由于该解决方案使用了WSF,批处理脚本可以包含任意数量的独立VBS、JScript或其他任务,可以选择性地调用每个任务。每个任务甚至可以使用多种语言。
<!-- : Begin batch script
@echo off
echo batch output
cscript //nologo "%~f0?.wsf" //job:JS
cscript //nologo "%~f0?.wsf" //job:VBS
exit /b

----- Begin wsf script --->
<package>
  <job id="JS">
    <script language="VBScript">
      sub vbsEcho()
        WScript.Echo "VBScript output called by JScript called by batch"
      end sub
    </script>
    <script language="JScript">
      WScript.Echo("JScript output called by batch");
      vbsEcho();
    </script>
  </job>
  <job id="VBS">
    <script language="JScript">
      function jsEcho() {
        WScript.Echo("JScript output called by VBScript called by batch");
      }
    </script>
    <script language="VBScript">
      WScript.Echo "VBScript output called by batch"
      call jsEcho
    </script>
  </job>
</package>

3
你的第二种解决方案使用 REM. 也不错,但有一个缺点。如果存在一个名为 REM(无扩展名)的文件,REM. 会导致一个 文件未找到 的错误。但是你可以改用 REM^:,这似乎总是有效的。(也可以用 REM^,REM^;REM^ - jeb
@jeb - 在我看来,没有插入符号的 REM: 永远不会失败,并且是最简单的。或者有一种模糊的情况会导致它失败吗? - dbenham
3
相信不相信,那行文字是合法的批处理标签。CMD.EXE充满了这样的怪癖。冒号前的字符是几个可以出现在批处理标签之前的字符之一,这是jeb在DosTips某个地方发现并发布的。如果你愿意深入了解,你可以在http://www.dostips.com/forum/viewtopic.php?f=3&t=3803上阅读各种疯狂的标签规则。 - dbenham
1
你是对的,REM^ 会失败。但是REM^, , REM^; 以及REM^: 似乎是安全的,我找不到它们失败的方法,而且它们仍然可以在同一行中使用& - jeb
1
@Paul - 关于12月28日的评论,涉及到<!-- : Begin...:这一行实际上不是一个可调用的标签。<!--被视为重定向输入,并且重定向可以出现在一行的任何位置。然而,解析器在内存中将其移动到末尾,这使得该行开头看起来像是 : Begin...,被解析为标签,因此该行不会被执行(也不会发生重定向)。 - dbenham
显示剩余10条评论

12

编辑:我的第一个答案对于 VBScript 是错误的,现在我尝试下一个...

使用 CTRL-Z 的想法不错,但我不喜欢在批处理文件中使用控制字符,因为复制和粘贴它们很麻烦。

这取决于您的浏览器、编辑器等因素。

您可以获取一个混合的 VBScript/Batch 文件,其中包含普通字符和“正常”的代码。

:'VBS/Batch Hybrid
:
:
:'Makes the next line only visible for VBScript ^
a=1_
<1' echo off <nul 

set n=nothing' & goto :'batchCode & REM Jump to the batch code

'VBScript-Part
wscript.echo "VB-Start"
wscript.echo "vb-End"
wscript.quit(0)

'Batch part
:'batchCode
set n=n'& echo Batch-Start
set n=n'& echo two
set n=n'& echo Batch-End
set n=n'& cscript /nologo /E:vbscript vbTest.bat
关键是在每个批处理行的开头加上set n=n'&,这对两种语言都是合法的,但vbs将忽略行的其余部分,只有批处理会执行行的其余部分。
另一种变体是:'remark ^,这是两者的注释,但对于批处理来说,此注释也是下一行的多行字符。VbScript看到a=1<1,行的其余部分是注释',而批处理仅看到<1' echo off <nul,第一个重定向1'将被第二个<nul覆盖,因此最终结果为echo off < nul
唯一剩下的问题是你可以看到第一个echo off,因为在重定向后使用@无法在批处理中工作。
对于JScript存在一个更简单的解决方案: hybrid scripting

我并不认为那对VBScript有帮助。JScript和VBScript有完全不同的语法。真正的问题是VBScript缺乏多行注释。如果脚本中任何地方暴露了单个批处理行,则VBScript将无法编译。 - dbenham
你说得对,我没有足够考虑VBScript的问题,我会将我的答案改为有帮助的变体。 - jeb
1
+1 太棒了。在 ECHO OFF 之后添加 &SETLOCAL 可能会有所改进。我还会将 n=nothing 移到第二行,并将批处理移动到顶部与 exit /b,但这只是外观上的问题。我仍然更喜欢 Ctrl-Z 的解决方案,因为它支持 @ECHO OFF,并且不设置任何多余的变量。丑陋的控制字符 vs. 不需要的 ECHO OFF 输出 - 我们每个人都必须选择自己的毒药 :-) - dbenham

4

这个问题有一个非常简单的解决方案。只需使用备用数据流(ADS)来存储VBS代码即可。简单说,ADS是另外一个地方,可以在同一文件中存储其他数据,也就是说,文件由其原始数据加上任意数量的额外ADS组成。每个ADS通过冒号与原始文件名分开以标识自己的名称。例如:

test.bat                    <- a file called "test.bat"
test.bat:script_.vbs        <- an ADS of file "test.bat" called "script_.vbs"

您可能会在网络上找到更加技术性和详细的ADS描述,但现在重要的是提到这个功能仅适用于NTFS磁盘。现在,让我们看看如何使用此功能来解决这个特定问题。

首先,按照惯例创建原始批处理文件。您也可以通过以下方式从命令行启动notepad.exe:

notepad test.bat

作为我的示例,我在test.bat中插入了以下行:

@echo off
setlocal

For /f "delims=" %%i in ('Cscript //nologo "test.bat:script_.vbs" "Select a folder"') do Set "folder=%%i"

echo Result: "%folder%"

请注意VBS脚本的名称。在保存了test.bat并关闭了记事本之后,使用记事本创建VBS脚本,因此在命令行中输入以下命令:

notepad test.bat:script_.vbs

按照惯例创建脚本:

Dim objFolder, objShell
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.BrowseForFolder(0, "Select a folder.", &H4000, 0)
If Not (objFolder Is Nothing) Then
   wscript.echo objFolder.Self.path
Else
   wscript.echo 0
End If

保存此文件并运行test.bat。它可以正常工作!;)

您可以通过dir命令中的/R开关来查看test.bat文件的ADS。您可以在此帖子中阅读更详细的说明以及该方法的限制。


现在我发现这非常有趣。而且出奇地狡猾。虽然我想不到立即使用的方法,但我会尝试将其存档以备将来使用。 - NapkinBob
1
@SachaDee:是的。我已经在第二段的第一句话中指出了:你可能会在网上找到更详细和技术性的ADS描述,但现在重要的是要提到这个功能仅适用于NTFS磁盘 - Aacini
抱歉!我没看到它! - SachaDee
3
不,你错了。这里只有一个文件,带有扩展名为 .bat 的那个! - Aacini
@Aacini 我知道了。很酷。 - TheBlastOne
显示剩余2条评论

3
我已经尝试将所有解决方案整合到一个脚本中,网址是 http://www.dostips.com/forum/viewtopic.php?p=37780#p37780
有一批处理脚本可以将最流行的语言转换为批处理文件(.js.vbs.ps1.wsf.hta和历史遗留的.pl)。
它的使用方法如下:
    :: 将filename1.vbs转换为可执行的filename1.bat
    cmdize.bat filename1.vbs


3

这是我的尝试(此处首次发布)。它类似于jeb的解决方案,但是(至少在我看来)代码更易读:

:sub echo(str) :end sub
echo off
'>nul 2>&1|| copy /Y %windir%\System32\doskey.exe ".\'.exe" >nul

'& echo/ 
'& cscript /nologo /E:vbscript %~f0 %*
'& echo/
'& echo BATCH: Translation is at best an ECHO.
'& echo/
'& pause
'& rem del /q ".\'.exe"
'& exit /b

WScript.Echo "VBScript: Remorse is the ECHO of a lost virtue."
WScript.Quit

并解释如下:

  1. ' (撇号)不是Windows中文件名禁止使用的符号,因此可以将文件命名为'.exe
  2. Windows中有一些带有命令行参数无法执行的命令。根据我的测试,最快速且大小最轻的命令是subst.exedoskey.exe
  3. 因此,在第一行,我将doskey.exe复制到'.exe(如果它不存在),然后将其继续使用作为'&(.exe扩展名应在%PATHEXT%中)。这将被视为VBScript中的注释,在批处理中不起作用-只会继续执行该行的下一个命令。

当然,有一些缺陷。至少在第一次运行时,您最终将需要管理员权限,因为在%windir%\System32\中的复制可能会被拒绝。为了保证鲁棒性,您还可以使用'>nul 2>&1|| copy /Y %windir%\System32\doskey.exe .\'.exe >nul,然后在结尾处使用:'& rem del /q .\'.exe

如果在路径中留下'.exe,您可能会不正确地使用它,并且每行代码的执行最终可能会降低性能。

..而且,在批处理部分,命令必须只在一行中。

更新9.10.13(..遵循上述约定)

这里还有一种需要批处理文件自我重命名的方法:

@echo off
goto :skip_xml_comment
<!--
:skip_xml_comment


    echo Echo from the batch

    ::start vbs code
    ( 
     ren %0 %0.wsf 
     cscript %0.wsf //nologo %*
     ren %0.wsf %0
    )


exit /b 0
-->

<package>
   <job id="vbs">
      <script language="VBScript">

         WScript.Echo "Echo from the VBS"

      </script>
   </job>
</package>

并附上简短的解释:

  1. 当批处理文件自我重命名并再次使用旧名称进行重命名时,并且这是在一行(或括号中)完成的,则脚本将无错误地执行。在这种情况下,还添加了一个调用{{link1:.WSF}}文件的CSCRIPT.EXE。自我重命名有点冒险,但比使用临时文件更快。
  2. Windows脚本宿主程序不太关心xml数据之外的项目,只要没有特殊的xml符号& < > ;,因此可以放心使用@echo offgoto。但为了安全起见,最好将批处理命令放在xml注释(或CDATA)部分中。为了避免批处理出现错误消息,在这里我跳过了<!--goto
  3. 在关闭xml注释之前,使用exit终止批处理脚本。

好处是不需要在单行命令中编写所有批处理代码,没有烦人的echo off显示,可以将jscript代码和不同的作业添加到脚本中。此外,也不需要特殊符号和创建额外的exe文件,如'.exe

另一方面,自我重命名可能存在风险。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接