Asked  6 Months ago    Answers:  5   Viewed   39 times

People have been embedding and executing VBScript within batch files for a long time. But all the published solutions that I have seen (at the time this question was originally posed) involve writing a temporary VBS file. For example: Embed VBScript inside Windows batch file.

Is it possible to execute embedded VBScript within batch without writing a temporary file?



Note - jump to the UPDATE 2014-04-27 section at the bottom of this answer for the best solution.

I used to think the answer was no. But then DosTips user Liviu discovered that the <SUB> character (Ctrl-Z, 0x1A, decimal 26) has bizare effects when embedded within a batch file. If functions somewhat like a line terminator such that it is possible for batch commands that follow a REM (or a :: remark hack) to execute if they are preceded by Ctrl-Z.

This has been confirmed on XP Home Edition sp3, Vista Home Premium sp2 64 bit, and Vista Enterprise sp2 32 bit. I'm assuming it works on other Windows versions.

Note - the code below is supposed to have embedded Ctrl-Z characters. I swear I used to see them on this site when viewed with IE8. But they seem to have been lost from this post somehow and I cannot figure out how to post them anymore. I've replaced the characters with the string <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

That is the key to a successful batch/vbs hybrid. As long as each batch command is preceded by rem<SUB> or ::'<SUB>, then the vbs engine won't see it, but the batch command will run. Just make sure you terminate the batch portion with an EXIT or EXIT /B. Then the remainder of the script can be normal looking vbs.

You can even have a batch label if needed. :'Label is both a valid vbs comment and a valid batch label.

Here is a trivial hybrid script. (again with <SUB> in place of embedded Ctrl-Z char)

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

Update 2012-04-15

jeb found a solution that avoids the awkward CTRL-Z, but it prints out ECHO OFF at the start and also sets some extraneous variables.

I have found a solution without CTRL-Z that eliminates the extraneous variables and is simpler to comprehend.

Normally the special characters &, |, <, > etc. don't work after a REM statement in batch. But the special characters do work after REM.. I found this nugget of information at A test shows that REM. is still a valid VBS comment. EDIT - based on jeb's comment, it is safer to use REM^ (there is a space after the caret).

So here is a trivial VBS/batch hybrid using REM^ &. The only drawback is it prints REM & at the beginning, whereas jeb's solution prints ECHO OFF.

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

Here is another trivial example that demonstrates multiple batch commands, including a CALL to a labeled sub-routine.

::' VBS/Batch Hybrid

::' --- Batch portion ---------
rem^ &@echo off
rem^ &call :'sub
rem^ &exit /b

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"

I still like the CTRL-Z solution because it eliminates all extraneous output.

UPDATE 2012-12-17

Tom Lavedas posted a method to conveniently run dynamic VBS from a batch script over at Google Groups: No file VBS hybrid scripting. The method uses mshta.exe (Microsoft HTML Application Host).

His original batch solution relied on an external small VBS.BAT script to execute the VBS within a FOR /F. I modified the syntax slightly to make it convenient to embed directly within any given batch script.

It is quite slow, but very convenient. It is restricted to executing a single line of VBS.

The VBS is written normally, except all quotes must be doubled: A quote enclosing a string must be written as "", and quotes internal to a string must be written as """". Normally the mini script is executed within the IN() clause of a FOR /F. It can be executed directly, but only if stdout has been redirected or piped.

It should work on any Windows OS from XP onward as long as IE is installed.

@echo off
:: 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

:: Get pi
for /f %%P in ('%vbsBegin% 4*atn(1) %vbsEnd%') do set PI=%%P
set PI

set "var=name=value"
echo Before - %var%
:: Replace =
for /f "delims=" %%S in (
  '%vbsBegin% replace(""%var%"",""="","": "") %vbsEnd%'
) do set "var=%%S"
echo After  - %var%

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! ]
  ) else echo(   %%N: Doesn't work :(

:: 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 "^"

UPDATE 2014-04-27

Over at DosTips there is a great compendium of js/vbs/html/hta hybrids and chimeras in cmd/bat. Lots of good stuff from various people.

Within that thread, DosTips user Liviu discovered a beautiful VBS/batch hybrid solution that uses 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"

I think this solution is fantastic. The batch and WSF sections are clearly separated by nice headers. The batch code is absolutely normal, without any odd syntax. The only restriction is the batch code cannot contain -->.

Similarly, the VBS code within WSF is absolutely normal. The only restriction is the VBS code cannot contain </script>.

The only risk is the undocumented use of "%~f0?.wsf" as the script to load. Somehow the parser properly finds and loads the running .BAT script "%~f0", and the ?.wsf suffix mysteriously instructs CSCRIPT to interpret the script as WSF. Hopefully MicroSoft will never disable that "feature".

Since the solution uses WSF, the batch script can contain any number of independent VBS, JScript, or other jobs that can be selectively called. Each job can even utilize multiple languages.

<!-- : 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 --->
  <job id="JS">
    <script language="VBScript">
      sub vbsEcho()
        WScript.Echo "VBScript output called by JScript called by batch"
      end sub
    <script language="JScript">
      WScript.Echo("JScript output called by batch");
  <job id="VBS">
    <script language="JScript">
      function jsEcho() {
        WScript.Echo("JScript output called by VBScript called by batch");
    <script language="VBScript">
      WScript.Echo "VBScript output called by batch"
      call jsEcho
Tuesday, June 1, 2021
answered 6 Months ago

This proof of concept script:

' pocBTicks.vbs - poor man's version of backticks (POC)

Option Explicit

' Globals

Const ForReading         =  1

Dim goFS  : Set goFS  = CreateObject( "Scripting.FileSystemObject" )
Dim goWSH : Set goWSH = CreateObject( "WScript.Shell" )

' Dispatch
WScript.Quit demoBTicks()

' demoBTicks -
Function demoBTicks()
  demoBTicks = 1
  Dim aCmds : aCmds = Array( _
      "dir pocBTicks.vbs" _
    , "dur pocBTicks.vbs" _
    , "xcopy /?" _
  Dim sCmd
  For Each sCmd In aCmds
      WScript.Echo "########", sCmd
      Dim aRet : aRet = BTicks( sCmd )
      Dim nIdx
      For nIdx = 0 To UBound( aRet )
          WScript.Echo "--------", nIdx
          WScript.Echo aRet( nIdx )
  demoBTicks = 0
End Function ' demoBTicks

' BTicks - execute sCmd via WSH.Run
'  aRet( 0 ) : goWSH.Run() result
'  aRet( 1 ) : StdErr / error message
'  aRet( 2 ) : StdOut
'  aRet( 3 ) : command to run
Function BTicks( sCmd )
  Dim aRet    : aRet     = Array( -1, "", "", "" )
  Dim sFSpec2 : sFSpec2  = goFS.GetAbsolutePathName( "." )
  Dim sFSpec1 : sFSpec1  = goFS.BuildPath( sFSpec2, goFS.GetTempName() )
                sFSpec2  = goFS.BuildPath( sFSpec2, goFS.GetTempName() )

  aRet( 3 ) = """%COMSPEC%"" /c """ + sCmd + " 1>""" + sFSpec1 + """ 2>""" +  sFSpec2 + """"""
  Dim aErr
 On Error Resume Next
  aRet( 0 ) = goWSH.Run( aRet( 3 ), SW_SHOWMINNOACTIVE, True )
  aErr      = Array( Err.Number, Err.Description, Err.Source )
 On Error GoTo 0
  If 0 <> aErr( 0 ) Then
     aRet( 0 ) = aErr( 0 )
     aRet( 1 ) = Join( Array( aErr( 1 ), aErr( 2 ), "(BTicks)" ), vbCrLf )
     BTicks    = aRet
     Exit Function
  End If

  Dim nIdx : nIdx = 1
  Dim sFSpec
  For Each sFSpec In Array( sFSpec2, sFSpec1 )
      If goFS.FileExists( sFSpec ) Then
         Dim oFile : Set oFile = goFS.GetFile( sFSpec )
         If 0 < oFile.Size Then
            aRet( nIdx ) = oFile.OpenAsTextStream( ForReading ).ReadAll()
            goFS.DeleteFile sFSpec
         End If
      End If
      nIdx = nIdx + 1
  BTicks = aRet
End Function

shows how to use .Run and temporary files to get something like backticks with a hidden console. Decent file handling, quoting in sCmd, cleaning of the returned strings, and dealing with encodings will require more work. But perhaps you can use the strategy to implement something that fits your needs.

Thursday, June 10, 2021
answered 6 Months ago

You can do it with for command and ~ syntax (see for /?):

(for /r %A in (*) do @echo %~tA %A ) | sort /r

The use of brackets allows for single redirection od whole for to sort. Without brackes every echo would be redirected to individual sort, so no sorting would be done.

EDIT: As Ekkehard.Horner pointed out, the above code will work only in regions where dates are printed in yyyy-mm-dd format. In a region where dates are printed in mm/dd/yyyy format, you can use the following batch file:

@Echo Off
setlocal enabledelayedexpansion

if "%1"=="list" goto :list
%0 list | sort /r

goto :EOF

for /r %%A in (*) do (
  set t=%%~tA
  echo !t:~6,4!-!t:~0,2!-!t:~3,2! %%A
goto :EOF

I did not manage to repeat the trick with brackets inside a batch file, so the script calls itself with a parameter causing it to print the list of files and then sorts the output. The dates are converted to yyyy-mm-dd format by using %variable~:start-length% syntax (see set /?) and delayed variables expansion. It is not as bullet-proof as dbenham's solution, but it works.

Saturday, August 21, 2021
answered 4 Months ago

Almost, with a small extension method.

static class StringExtensions
    public static string PHPIt<T>(this string s, T values, string prefix = "$")
        var sb = new StringBuilder(s);
        foreach(var p in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
            sb = sb.Replace(prefix + p.Name, p.GetValue(values, null).ToString());
        return sb.ToString();

And now we can write:

string foo = "Bar";
int cool = 2;

var result = "This is a string $foo with $cool variables"
             .PHPIt(new { 

//result == "This is a string Bar with 2 variables"
Tuesday, September 28, 2021
answered 2 Months ago

Is it possible to create and maintain a folder structure with files Using the Internal Storage

Yes, using standard Java I/O.

Or maybe tell me why the example below fails miserably

Talented programmers know how to describe symptoms, rather than use pointless phrases like "fails miserably".

That being said, file.mkdirs(); creates a directory. You then try opening that directory as if it were a file, for the purposes of writing data to it. That does not work on any OS that I am aware of, and certainly not on Android. Please call mkdirs() on something that will create the file's parent directory (e.g., file.getParentFile().mkdirs()).

Also, never use concatenation to create a File object. Use the proper File constructor.

Thursday, November 25, 2021
William George
answered 4 Days ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :