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

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

评论

那么PowerShell + VBScript呢? -> stackoverflow.com/questions/63514534 / ...

#1 楼

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

我以前认为答案是否定的。但是,随后DosTips用户Liviu发现,将<SUB>字符(Ctrl-Z,0x1A,十进制26)嵌入到批处理文件中时会产生bizare效果。如果函数有点像行终止符,那么在REM(或:::注释hack)之后的批处理命令如果在Ctrl-Z之前可以执行。 http://www.dostips.com/forum/viewtopic.php?p=13160#p13160

已在XP Home Edition sp3、64位Vista Home Premium sp2和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终止批次部分即可。然后,脚本的其余部分可以看起来很普通。

如果需要,您甚至可以具有批处理标签。 :'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。这是另一个简单的示例,它演示了多个批处理命令,包括从CALL到CALL的调用。带标签的子例程。

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


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

UPDATE 2012-12- 17

Tom Lavedas在Google网上论坛发布了一种从批处理脚本方便地运行动态VBS的方法:没有文件VBS混合脚本。该方法使用mshta.exe(Microsoft HTML应用程序主机)。

他的原始批处理解决方案依赖于外部小型VBS.BAT脚本在FOR / F中执行VBS。我对语法进行了少许修改,以方便直接嵌入任何给定的批处理脚本中。

速度很慢,但是非常方便。只能执行一行VBS。

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

只要安装了IE,它就可以在XP以后的任何Windows操作系统上运行。

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


更新2014-04-27

在DosTips上,有大量的js / vbs / html / hta混合体和cmd / bat中的嵌合体。来自不同人群的许多好东西。

在该线程中,DosTips用户Liviu发现了一种使用WSF的精美的VBS /批处理混合解决方案。

@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


我认为这个解决方案很棒。批处理和WSF部分明显由漂亮的标题分开。批处理代码绝对是正常的,没有任何奇怪的语法。唯一的限制是批处理代码不能包含-->。类似地,WSF中的VBS代码也是绝对正常的。唯一的限制是VBS代码不能包含</script>

唯一的风险是无证使用"%~f0?.wsf"作为要加载的脚本。解析器以某种方式正确地找到并加载了正在运行的.BAT脚本"%~f0",后缀?.wsf神秘地指示CSCRIPT将脚本解释为WSF。希望MicroSoft永远不会禁用该“功能”。

由于该解决方案使用WSF,所以批处理脚本可以包含任意数量的独立VBS,JScript或可以有选择地调用的其他作业。每个工作甚至可以使用多种语言。

<!-- : 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>


评论


REM的第二种解决方案。也不错,但有缺点。如果存在名称为REM(无扩展名)的文件,则为REM。结果变成找不到文件错误。但是您可以使用REM ^:相反,这似乎总是可行的。 (也是REM ^,REM ^ ;、 REM ^)

–杰布
2012-04-15 22:44

@jeb-在我看来,REM:没有插入符号就永远不会失败,这是最简单的。还是有一种晦涩的情况会失败?

– dbenham
13年11月1日在22:12

@Paul-信不信由你,那行是有效的批次标签。 CMD.EXE充满了这样的特质。冒号之前的字符是少数可以出现在批处理标签之前的字符-杰布发现这些东西并将其张贴在DosTips上的某个位置。如果您愿意钻进兔子洞,则可以在dostips.com/forum/viewtopic.php?f=3&t=3803上阅读有关疯狂标签规则的信息

– dbenham
2014年12月28日在18:28

没错,REM ^失败。但是REM ^,REM ^;以及REM ^:似乎很安全,我找不到它们失败的方法,并且仍然可以在同一行中使用&

–杰布
16年1月14日上午10:57

@Paul-关于12月28日有关<!-的评论::开始...:该行并不是真正可调用的标签。 <!-被视为重定向输入,并且重定向可以出现在一行的任何地方。但是,解析器将其移动到内存的末尾,使得行的开始看起来像`:Begin ...`,它被解析为标签,因此该行不会执行(并且重定向永远不会发生)。

– dbenham
17/12/30在4:44



#2 楼

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

使用CTRL-Z的好主意,但我不喜欢批处理文件中的控制字符,因为这是有问题的复制并粘贴它们。
取决于您的浏览器,编辑器,...

,您还可以获得带有普通字符和“普通”代码的混合VBScript /批处理。

:'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'&,这对于两者都是合法的,但是vb将忽略该行的其余部分,只有批处理将执行其余部分

另一个变体是:'remark ^,这是对这两者的说明,但是对于批处理,它也用多行字符也说明了下一行。
VbScript会看到a=1<1的其余部分。该行是备注'
该批处理仅看到<1' echo off <nul,来自1'的第一个重定向将被第二个<nul覆盖,因此结果仅是echo off < nul

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

对于JScript,存在一个更简单的方法解决方案混合脚本

评论


我看不出这对VBscript有什么帮助。 Jscript和VBscript具有完全不同的语法。真正的问题是VBscript缺少多行注释。如果单个批处理行暴露在脚本中的任何位置,则VBscript不会编译。

– dbenham
2012年1月31日12:15

您是对的,之前对vbscript的思考不够充分,我将答案更改为有用的变体

–杰布
2012年2月1日下午6:31

+1辉煌。可以通过在ECHO OFF之后添加&SETLOCAL来改进。我也将n = nothing移至第二行,并使用/ b出口将批次移至顶部,但这很漂亮。我仍然更喜欢Ctrl-Z解决方案,因为它支持@ECHO OFF,并且因为它没有设置任何无关的变量。丑陋的控制字符与不需要的ECHO OFF输出-我们每个人都必须选择毒药:-)

– dbenham
2012年2月2日,下午3:12

#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 楼

这个问题有一个非常简单的解决方案。只需使用备用数据流(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"


您可能会在Web上找到有关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。您可以在这篇文章中阅读有关此方法及其局限性的更广泛的解释。

评论


现在,我发现这很有趣。由于某种原因,偷偷摸摸。虽然我想不起来立即使用,但我将尝试将其归档以备将来使用。

– NapkinBob
18年4月11日在14:59

仅适用于NTFS!

– SachaDee
18年4月13日在14:09

@SachaDee:是的。我已经在第二段的第一句中指出:您可能会在Web上找到有关ADS的更多技术性和详细描述,但是现在重要的是要提到此功能仅适用于NTFS磁盘。

–Aacini
18年4月13日在14:12

糟糕!没看到抱歉!

– SachaDee
18年4月13日在14:14

@TheBlastOne:不,你错了。这里只有一个文件,一个扩展名为.bat的文件!

–Aacini
18年7月20日在20:01

#5 楼

我的尝试(首先发布在这里)。它类似于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(如果尚不存在)
,然后继续将其用作'&(因为.exe扩展名应位于%PATHEXT%中)。 VBScript中的批处理注释将无济于事-只需继续执行该行中的下一个命令即可。

当然还有一些缺陷。至少在第一次运行时,最终您将需要管理员权限,因为%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 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>


并简短说明:


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


所以好事情是不需要在单行命令中编写所有批处理代码,
不会显示烦人的echo off,可以向其中添加jscript代码和其他作业也不需要特殊符号和创建其他exe文件,例如'.exe

另一方面,自我重新命名可能会带来风险。

#6 楼

我没有VB.NET对VBScript的精确计数,但是它绝对是带有某些C#风格的VisualBasic代码。
msbuild版本中,有一个称为内联任务的东西,允许您加载.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()