是否可以在批处理文件中嵌入和执行VBScript而不使用临时文件?

时间:2012-01-31 04:39:58

标签: vbscript batch-file

人们已经在批处理文件中嵌入和执行VBScript很长一段时间了。但是我所看到的所有已发布的解决方案(当时这个问题最初提出时)涉及编写临时VBS文件。例如:Embed VBScript inside Windows batch file

是否可以在批处理中执行嵌入式VBScript而无需编写临时文件?

6 个答案:

答案 0 :(得分:51)

注 - 请跳至本答案底部的 更新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 /批处理混合。唯一的缺点是它在开头打印REM &,而jeb的解决方案打印ECHO OFF

rem^ &@cscript //nologo //e:vbscript "%~f0" & exit /b
WScript.Echo "Example of a hybrid VBS / batch file"

这是另一个简单的示例,它演示了多个批处理命令,包括对标记的子例程的CALL。

::' 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)

我仍然喜欢CTRL-Z解决方案,因为它消除了所有无关的输出。

更新2012-12-17

Tom Lavedas发布了一种方法,可以通过Google网上论坛的批处理脚本方便地运行动态VBS:No file VBS hybrid scripting。该方法使用mshta.exe(Microsoft HTML Application Host)。

他最初的批处理解决方案依赖于外部小型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 hybrids and chimeras in cmd/bat概要。来自不同人的很多好东西。

在该线程中,DosTips用户Liviu发现了一个使用WSF的beautiful VBS/batch hybrid解决方案。

<!-- : 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。希望MicroSoft永远不会禁用该“功能”。

由于解决方案使用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>

答案 1 :(得分:11)

编辑:我的第一个回答是错误的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

答案 2 :(得分:3)

我的尝试(first posted here)。它类似于jeb的解决方案,但(至少根据我)代码更具可读性:

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

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

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

并解释:

  1. ' (单引号)不是禁止符号作为Windows中文件名的一部分,因此有一个名为'.exe的文件没有问题
  2. 没有命令行参数的命令包含很少的命令。最快(无所事事)和最轻的大小(根据我的测试)是subst.exedoskey.exe < / LI>
  3. 所以在第一行我正在处理doskey.exe'.exe(如果它还不存在的话) 然后继续将其用作'&(因为.exe扩展名应该在%PATHEXT%中)。这将作为VBScript中的注释而批处理将无效 - 只是将继续下一行命令。
  4. 当然存在一些缺陷。至少在第一次运行时最终你需要管理员权限,因为%windir%\System32\中的应对可能会被拒绝。为了健壮,你也可以使用'>nul 2>&1|| copy /Y %windir%\System32\doskey.exe .\'.exe >nul然后在结束: '& rem del /q .\'.exe

    如果你的路径中留有'.exe,你可能无意中以不正当的方式使用它,并且'.exe的每一行上的执行最终都会降低性能。

    ..在批处理中,命令必须只在一行中。

    更新9.10.13(..遵循上述惯例)

    这是另一种需要自行重命名批处理文件的方法:

    @echo off
    goto :skip_xml_comment
    <!--
    :skip_xml_comment
    
    echo(
    echo Echo from the batch
    echo(
    
    ( ren %0 %0.wsf 
     cscript %0.wsf 
     ren %0.wsf %0 )
    
    
    exit /b 0
    -->
    
    <package>
       <job id="vbs">
          <script language="VBScript">
    
             WScript.Echo "Echo from the VBS"
    
          </script>
       </job>
    </package>
    

    简短的解释:

    1. 当批量自动重命名并使用旧名称重新命名时,这将在一行(或括号中)完成,脚本将执行而没有错误。在这种情况下,还添加了一个{{{带有.WSF文件的1}}。自我重命名有点风险,但比临时文件更快。
    2. 只要没有特殊的xml符号CSCRIPT.EXE,Windows脚本主机就不关心xml数据之外的项目,因此可以毫无后顾之忧地使用& < > ;@echo off。但是为了安全起见,将批处理命令放在xml注释(或CDATA)部分是很好的。为了避免批处理中的错误消息,我在此处跳过goto <!--
    3. 在关闭xml注释之前,批处理脚本以goto
    4. 终止

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

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

答案 3 :(得分:3)

我试图在http://www.dostips.com/forum/viewtopic.php?p=37780#p37780的一个脚本中汇集所有解决方案。

批处理脚本将最流行的语言转换为批处理文件( .js .vbs .ps1 .wsf .hta 和历史 .pl )。

它的工作原理如下:


    :: convert filename1.vbs to executable filename1.bat
    cmdize.bat filename1.vbs

答案 4 :(得分:2)

这个问题有一个非常简单的解决方案。只需使用备用数据流(ADS)来存储VBS代码。简单地解释一下,ADS是另一个地方,您可以在其中存储其他数据在同一个文件中,也就是说,文件由其原始数据加上任意数量的数据组成其他ADS&#39> 。识别每个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。有用!的 ;)

您可以通过test.bat命令中的/R开关查看dir文件的ADS。您可以在this post阅读有关此方法及其局限性的更广泛说明。

答案 5 :(得分:0)

我不算VB.NET对VBScript的准确计数,但是绝对是带有C#风格的VisualBasic代码。

msbuild版本中,有一个叫做inline tasks的东西,您可以将.net代码加载到内存中并执行它。可以使用与wsf相似的方式将其嵌入到批处理文件中:

<!-- :
        @echo off
        echo -^- FROM BATCH

        set "CMD_ARGS=%*"
        :::::::::::::::::  Starting VB.NET code :::::::::::::::::::::::
        ::
        :: searching for msbuild location
        for /r "%SystemRoot%\Microsoft.NET\Framework\" %%# in ("*msbuild.exe") do  set "msb=%%#"

        if not defined  msb (
           echo no .net framework installed
           exit /b 10
        )

        ::::::::::  calling msbuid :::::::::
        call %msb% /nologo  /noconsolelogger "%~dpsfnx0"
        ::
        ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        exit /b %errorlevel%
      
--> 


<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="T">
    <T/>
  </Target>
  <UsingTask
    TaskName="T"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll" > 
    <Task>
     <!-- include this for UI programs -->
     <!-- Reference Include="$(MSBuildToolsPath)\System.Windows.Forms.dll"/ -->
      <Using Namespace="System"/>
      <Code Type="Fragment" Language="vb">
        <![CDATA[
            '''' VB CODE STARTS HERE ''''
            
            Console.WriteLine("-- FROM VB.NET"):
            
            '''''''''''''''''''''''''''''
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

尽管xml语法允许将几乎所有内容都放在一行(如果有人愿意的话)的确确实很冗长(但是出于教育的原因,我最好将这个示例保留为这样)。批处理代码不能包含--,因此出于鲁棒性考虑,该部分也可以放在CDATA中。

在没有临时文件的情况下将VB.NET嵌入批处理的其他方法是借助powershell及其类型定义cmdlet:

<# : batch portion
@echo off & setlocal

set "CMD_ARGS=%~1"

powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF

: end batch / begin powershell #>

param($psArg1 = $env:psArg1)


$VB = @" 
Imports System
namespace VB 
    Public Class VB
        Public Shared Sub Print()
            Console.WriteLine("PRINTED BY VB")
        End Sub
    End Class
End Namespace
"@

Add-Type -TypeDefinition $VB -Language VisualBasic


[VB.VB]::Print()