Можно ли встраивать и выполнять VBScript в пакетном файле без использования временного файла?

Люди уже давно встраивают и выполняют VBScript в пакетных файлах. Но все опубликованные решения, которые я видел (в то время, когда этот вопрос был задан), включают в себя создание временного файла VBS. Например: встроить VBScript в пакетный файл Windows.

Можно ли выполнить встроенный VBScript в пакетном режиме без записи временного файла?

5 ответов

Решение

Примечание. Перейдите к разделу ОБНОВЛЕНИЕ 2014-04-27 в нижней части этого ответа, чтобы найти лучшее решение.

Раньше я думал, что ответ был нет. Но затем пользователь DosTips Ливиу обнаружил, что <SUB> символ (Ctrl-Z, 0x1A, десятичное число 26) имеет причудливые эффекты при внедрении в пакетный файл. Если функции в некоторой степени похожи на терминатор строки, то есть возможно выполнение пакетных команд, следующих за REM (или хаком:: замечание), если им предшествует Ctrl-Z. http://www.dostips.com/forum/viewtopic.php?p=13160

Это было подтверждено для 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 его не увидит, но запустится пакетная команда. Просто убедитесь, что вы заканчиваете пакетную часть EXIT или же EXIT /B, Тогда оставшаяся часть скрипта может быть нормально выглядящей VBS.

Вы даже можете иметь этикетку партии, если это необходимо. :'Label является действительным комментарием VBS и действительным ярлыком пакета.

Вот тривиальный гибридный скрипт. (снова с <SUB> вместо встроенного Ctrl-Z char)

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

Обновление 2012-04-15

Джеб нашел решение, которое позволяет избежать неуклюжего нажатия CTRL-Z, но вначале выводит ECHO OFF, а также устанавливает некоторые посторонние переменные.

Я нашел решение без CTRL-Z, которое устраняет посторонние переменные и проще для понимания.

Обычно специальные символы &, |, <, > и т.д. не работают после REM выписка в пакетном режиме. Но специальные символы работают после REM., Я нашел этот кусок информации на http://www.dostips.com/forum/viewtopic.php?p=3500. Тест показывает, что REM. все еще действительный комментарий VBS. РЕДАКТИРОВАТЬ - на основе комментария Джеба, это безопаснее использовать REM^ (после каретки есть место).

Итак, вот тривиальный гибрид VBS/ партия с использованием REM^ &, Единственный недостаток - это печать REM & в начале, в то время как решение Джеба печатает 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) опубликовал метод для удобного запуска динамического VBS из пакетного сценария в группах Google: нет гибридных сценариев VBS для файлов. Метод использует mshta.exe (узел приложения Microsoft HTML).

Его оригинальное пакетное решение основывалось на внешнем небольшом сценарии VBS.BAT для выполнения VBS в FOR /F. Я немного изменил синтаксис, чтобы было удобно встраивать его непосредственно в любой пакетный скрипт.

Это довольно медленно, но очень удобно. Он ограничен выполнением одной строки VBS.

VBS пишется нормально, за исключением того, что все кавычки должны быть удвоены: кавычка со строкой должна быть записана как "" и кавычки внутри строки должны быть записаны как """", Обычно мини-скрипт выполняется в предложении IN() FOR /F. Это может быть выполнено напрямую, но только если stdout был перенаправлен или передан по каналу.

Он должен работать на любой операционной системе Windows начиная с XP, если установлен IE.

@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, Ливиу, обнаружил красивое гибридное решение 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>

Я думаю, что это решение фантастическое. Секции batch и WSF четко разделены красивыми заголовками. Пакетный код абсолютно нормальный, без какого-либо странного синтаксиса. Единственное ограничение - код партии не может содержать -->,

Точно так же код VBS в WSF абсолютно нормален. Единственное ограничение - код 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>

РЕДАКТИРОВАТЬ: мой первый ответ был неправильным для 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 существует более простое решение гибридного скриптинга

Существует очень простое решение этой проблемы. Просто используйте альтернативный поток данных (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, Оно работает! ;)

Вы можете просмотреть объявления о test.bat подать через /R включить dir команда. Вы можете прочитать более подробное объяснение этого метода и его ограничений в этом посте.

И моя попытка ( впервые опубликованная здесь). Это похоже на решение 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.exe а также doskey.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 оставив на своем пути вы можете непреднамеренно использовать его ненадлежащим образом, а выполнение в каждой строке '.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. Когда пакет автоматически переименовывается и снова переименовывается со старым именем, и это делается в одну строку (или в скобках), сценарий будет выполнен без ошибок. В этом случае добавляется также один вызов CSCRIPT.EXE с .WSF file.Self-переименование немного рискованно, но быстрее, чем временный файл.
  2. Хост скриптов Windows не заботится о элементах вне данных XML, если нет специальных символов XML & < > ; так @echo off а также goto можно использовать без забот. Но в целях безопасности полезно поместить пакетные команды в раздел комментариев xml (или CDATA). Чтобы избежать сообщений об ошибках из пакета, я пропустил <!-- с goto Вот.
  3. Перед закрытием xml комментарий пакетный скрипт завершается с exit

Итак, хорошо то, что нет необходимости писать весь пакетный код в однострочных командах, нет раздражающих echo off отображаемый, код jscript и различные задания могут быть добавлены в скрипт. Также не нужны специальные символы и создание дополнительных exe-файлов, таких как '.exe

С другой стороны, самопереименование может быть рискованным.

Я попытался собрать все решения в один сценарий на http://www.dostips.com/forum/viewtopic.php?p=37780.

Существует пакетный скрипт, преобразующий наиболее популярные языки в пакетный файл (.js, .vbs, .ps1, .wsf, .hta и исторический .pl).

Это работает следующим образом:

:: конвертировать filename1.vbs в исполняемый файл filename1.bat
    cmdize.bat filename1.vbs

Я не знаю, сколько именно VB.NET учитывает для VBScript, но это определенно код VisualBasic с некоторыми разновидностями C#.

С версией 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 и командлет определения его типа:

<# : 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()
Другие вопросы по тегам