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