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

Posted

技术标签:

【中文标题】是否可以在不使用临时文件的情况下在批处理文件中嵌入和执行 VBScript?【英文标题】:Is it possible to embed and execute VBScript within a batch file without using a temporary file? 【发布时间】:2012-02-22 20:57:15 【问题描述】:

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

是否可以在不写入临时文件的情况下批量执行嵌入的 VBScript?

【问题讨论】:

PowerShell + VBScript 怎么样? --> ***.com/questions/63514534/… 【参考方案1】:

注意 - 跳转到此答案底部的 UPDATE 2014-04-27 部分以获得最佳解决方案。

我曾经认为答案是否定的。但随后 DosTips 用户 Liviu 发现 <SUB> 字符(Ctrl-Z,0x1A,十进制 26)在嵌入批处理文件时具有奇怪的效果。 If 函数有点像行终止符,这样如果在 REM(或 :: remark hack)之前有 Ctrl-Z 的话,批处理命令就有可能执行。 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&lt;SUB&gt;::'&lt;SUB&gt;,那么vbs 引擎就不会看到它,但是批处理命令会运行。只需确保使用EXITEXIT /B 终止批处理部分。那么脚本的其余部分就可以是正常的vbs了。

如果需要,您甚至可以使用批次标签。 :'Label 既是有效的 vbs 注释,也是有效的批处理标签。

这是一个简单的混合脚本。 (再次用 &lt;SUB&gt; 代替嵌入的 Ctrl-Z 字符)

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

2012 年 4 月 15 日更新

jeb 找到了一个避免笨拙的 CTRL-Z 的解决方案,但它在开始时打印出 ECHO OFF 并且还设置了一些无关变量。

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

通常特殊字符&amp;|&lt;&gt; 等在批处理REM 语句后不起作用。但是特殊字符在REM. 之后确实有效。我在http://www.dostips.com/forum/viewtopic.php?p=3500#p3500 找到了这些信息。测试表明REM. 仍然是有效的 VBS 评论。 编辑 -根据jeb的评论,使用REM^更安全(插入符号后面有一个空格)。

所以这是一个使用REM^ &amp; 的简单 VBS/batch 混合。唯一的缺点是它在开头打印REM &amp;,而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 Groups 上发布了一种通过批处理脚本方便地运行动态 VBS 的方法:No file VBS hybrid scripting。该方法使用 mshta.exe (Microsoft html Application Host)。

他最初的批处理解决方案依靠外部小型 VBS.BAT 脚本在 FOR /F 中执行 VBS。我稍微修改了语法以方便直接嵌入到任何给定的批处理脚本中。

速度很慢,但很方便。仅限于执行单行VBS。

VBS 写法正常,除了所有引号必须加倍:包含字符串的引号必须写为"",字符串内部的引号必须写为""""。通常,迷你脚本在 FOR /F 的 IN() 子句中执行。它可以直接执行,但前提是标准输出已被重定向或管道。

只要安装了 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 年 4 月 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 部分由漂亮的标题清楚地分开。批处理代码绝对正常,没有任何奇怪的语法。唯一的限制是批处理代码不能包含--&gt;

同样,WSF 内的 VBS 代码也是绝对正常的。唯一的限制是VBS代码不能包含&lt;/script&gt;

唯一的风险是未记录地使用 "%~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>

【讨论】:

您使用REM. 的第二个解决方案也不错,但有一个缺点。如果存在名称为 REM(无扩展名)的文件,REM. 将导致 file not found 错误。但是您可以改用REM^:,这似乎总是有效。 (还有REM^,REM^;REM^ @jeb - 在我看来,REM: 没有插入符号永远不会失败,并且是最简单的。或者是否存在确实失败的模糊情况? @Paul - 信不信由你,该行是有效的批次标签。 CMD.EXE 充满了这样的特质。冒号之前的字符是可以出现在批次标签之前的少数字符之一 - 这是 jeb 发现并发布在 DosTips 上的某处。如果你愿意进入兔子洞,那么你可以在dostips.com/forum/viewtopic.php?f=3&t=3803阅读大量疯狂的标签规则@ 你是对的,REM^ 失败了。但是REM^, REM^; REM^: 似乎是安全的,我找不到它们失败的方法,它们仍然可以在同一行中使用 &amp; @Paul - 关于 12 月 28 日涉及 &lt;!-- : Begin... 的 cmets:该行并不是真正的可调用标签。 &lt;!-- 被视为重定向输入,并且重定向可以出现在一行的任何位置。但是,解析器将其移动到内存中的末尾,这使行的开头看起来像` : Begin ...`,它被解析为标签,因此不执行该行(并且永远不会发生重定向)。【参考方案2】:

编辑:我的第一个答案对于 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'&amp;,这对两者都是合法的,但是 vbs 会忽略该行的其余部分,只有批处理会执行该行的其余部分。

另一个变体是:'remark ^,这是对两者的注释,但对于批处理,此注释也是多行字符的下一行。 然后 VbScript 看到 a=1&lt;1 行的其余部分是注释 ' 该批次只看到&lt;1' echo off &lt;nul,来自1' 的第一个重定向将被第二个&lt;nul 覆盖,因此它只导致echo off &lt; nul

唯一剩下的问题是您可以看到第一个echo off,因为在重定向后无法批量使用@

对于 JScript 存在一个更简单的解决方案 hybrid scripting

【讨论】:

我看不出这对 VBscript 有什么帮助。 Jscript 和 VBscript 具有完全不同的语法。真正的问题是 VBscript 缺少多行注释。如果单个批处理行在脚本中的任何位置公开,则 VBscript 将不会编译。 你说得对,之前对vbscript有问题的考虑不够,我会把答案改成有用的变体 +1 太棒了。可以通过在 ECHO OFF 后添加 &amp;SETLOCAL 来改进。我还将 n=nothing 移动到第二行,并使用 exit /b 将批次移动到顶部,但这是装饰性的。我仍然更喜欢 Ctrl-Z 解决方案,因为它支持@ECHO OFF,并且没有设置任何无关变量。丑陋的控制字符与不需要的 ECHO OFF 输出 - 我们每个人都必须选择我们的毒药:-)【参考方案3】:

我的尝试 (first posted here)。它类似于 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

以及解释:

    '(单引号)在 Windows 中不是作为文件名一部分的禁止符号,因此拥有一个名为 '.exe 的文件没有问题 在没有命令行参数的情况下,很少有包含 Windows 的命令。最快(什么都不做)和最轻量级(根据我的测试)是 subst.exedoskey.exe 所以在第一行我将doskey.exe 处理为'.exe(如果它不存在) 然后继续将其用作'&amp;(因为 .exe 扩展名应位于 %PATHEXT% 中)。这将被视为 VBScript 中的注释,并且批量不会执行任何操作 - 只会继续下一条命令就行了。

当然有一些缺陷。至少在第一次运行时,最终您将需要管理员权限,因为%windir%\System32\ 中的应对可能会被拒绝。为了稳健性,您还可以使用'&gt;nul 2&gt;&amp;1|| copy /Y %windir%\System32\doskey.exe .\'.exe &gt;nul,然后在最后: '&amp; rem del /q .\'.exe

'.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>

还有一个简短的解释:

    当批次自重命名并再次使用旧名称重命名时,这在一行(或括号中)中完成,脚本将被正确执行。在这种情况下,还添加了一次调用 @987654335 @ with .WSF file.Self-renameing 有点冒险,但比临时文件快。 Windows 脚本宿主不太关心 xml 数据之外的项目,只要没有特殊的 xml 符号&amp; &lt; &gt; ; 所以@echo offgoto 可以放心使用。但为了安全起见,最好将批处理命令放在 xml 注释(或 CDATA)部分。为了避免批处理中的错误消息,我在这里跳过了 &lt;!--goto。 在关闭 xml 注释批处理脚本之前以 exit 终止

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

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

【讨论】:

【参考方案4】:

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

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

它的工作原理如下:

:: 将文件名1.vbs 转换为可执行文件名1.bat cmdize.bat 文件名1.vbs

【讨论】:

【参考方案5】:

这个问题有一个非常简单的解决方案。只需使用 备用数据流 (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。您可以在this post 阅读有关此方法及其局限性的更详细说明。

【讨论】:

现在我觉得这很有趣。出于某种原因,偷偷摸摸。虽然我想不出立即使用,但我会尝试将其归档以备将来使用。 仅适用于 NTFS ! @SachaDee:是的。我已经在第二段的第一句话中指出:您可能会在网络上找到关于 ADS 的更技术性和更广泛的描述,但现在重要的是要提到此功能仅适用于 NTFS 磁盘. 哎呀!!没看到抱歉! @TheBlastOne:不,你错了。这里只有一个文件,扩展名为.bat【参考方案6】:

我不知道 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()

【讨论】:

以上是关于是否可以在不使用临时文件的情况下在批处理文件中嵌入和执行 VBScript?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在不使用情节提要的情况下在 xib 文件中创建原型内容 UITableview?

我可以在不使用 xml 文件的情况下在列表视图中添加视图吗

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

如何在不安装的情况下在应用程序中使用 Berkeley DB

如何在不使用临时表或视图的情况下在多个列上使用 PIVOT [重复]

如何在不启动模拟器的情况下在 Eclipse 中将 Android 项目编译为 .apk 文件?