3
votes

I recently learned a lot messing with batch + VBScript hybrid scripts and while it was great learning and works, it's time to learn PowerShell more thoroughly. But, my favorite part of Batch/VBScript solution is that I can create a single script.cmd file to distribute.

Is there any sort of solution with PowerShell/VBScript? Ideally I think I'd prefer a .ps1 script with embedded VBScript, but interested in knowing my options.

There seems to be some confusion regarding the goal.

  • One Single File (This is the most important part)
  • Extension either .ps1 or .vbs
  • Both POWERSHELL and VBScript inside single file
  • Bonus:
    • No writing to external file
    • Prefacing each line
    • Having to escape special characters in code
    • Encoding entire sections of script (overhead CPU intensive operations)

Here is a thoeretical example:

script.{ps1/vbs}:

<!-- : Begin PS1 script
$strString = "Hello PowerShell"
write-host $strString

cscript //nologo "%~f0?.wsf" //job:HELLOWORLD
exit /b
PAUSE
----- Begin wsf script --->
<package>
  <job id="HELLOWORLD">
    <script language="VBScript">
      MsgBox "Hello World VBS"
    </script>
  </job>
  <job id="VBS">
    <script language="VBScript">
        'Second Script!
    </script>
  </job>
</package>

Something like this -->

https://stackoverflow.com/a/9074483/5079799

<!-- : Begin batch script
@ECHO OFF
CLS

cscript //nologo "%~f0?.wsf" //job:HELLOWORLD
exit /b
PAUSE
----- Begin wsf script --->
<package>
  <job id="HELLOWORLD">
    <script language="VBScript">
      MsgBox "Hello World"
    </script>
  </job>
  <job id="VBS">
    <script language="VBScript">
        'Second Script!
    </script>
  </job>
</package>
4
Why tho? I can't think of anything you can do in VBScript that you can't do in Powershell.Nick.McDermaid
I'm learning that as I look more at converting some of my scripts. An initial thought might be to programmatically migrate sections of my code while using the old VB code simultaneously. @Nick.McDermaidFreeSoftwareServers

4 Answers

2
votes

Create the VBS script as usual. Save in some location and then convert it into Base64. Byte encoding is used so that this will work on binary files too, and overcomes character encoding issues. Like so,

$Content = Get-Content -Path C:\temp\myScript.vbs -Encoding Byte
$Base64 = [System.Convert]::ToBase64String($Content)
$Base64 | Out-File c:\temp\myScript.b64

Then, in your Powershell script, include the encoded version of the VBS script. Convert Base64 back into string and write it into a file. Finally, call cscript to run the .vbs.

$Base64 = "ZgB1AG4AYwB0AGkAbwBuACAAR..."
$Content = [System.Convert]::FromBase64String($Base64)
Set-Content -Path $env:temp\myScript.vbs -Value $Content -Encoding Byte

& cscript /nologo $env:temp\myScript.vbs 

Another an option is to embed the VBScript in a here-string like so,

# Paste the VBS in a here string
$Content = @'
dim foo
...
'@
Set-Content -Path $env:temp\myScript.vbs -Value $Content
& cscript /nologo $env:temp\myScript.vbs 
1
votes

Perhaps, you mean create a .ps1 script file and run it from vbscript ?

If so, here is an example named as Compress_Archive_by_Extension.vbs

Remark : Compress-Archive is only available with PS v4


Option Explicit
Dim Title,ArrExt,Ext
Title = "Compress Archive With Powreshell And Vbscript by Hackoo 2020"
REM We define an array of extensions for archiving !
ArrExt = Array("vbs","vbe","cmd","bat","ps1","js","jse","lnk")

REM Looping thru extensions defined from our array in order to zip and archive them, 
REM so you can add or remove what you want as extension in the array above !
For each Ext in ArrExt
    Call Compress_Archive("%Temp%\*."& Ext,"Temp_Archive_"& Ext)
    Call Compress_Archive("%AppData%\*."& Ext,"AppData_Archive_"& Ext)
    Call Compress_Archive("%LocalAppData%\*."& Ext,"LocalAppData_Archive_"& Ext)
    Call Compress_Archive("%ProgramData%\Microsoft\Windows\Start Menu\Programs\Startup\*."& Ext,"ProgramData_Archive_"& Ext)
    Call Compress_Archive("%UserProfile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\*."& Ext,"UserProfile_Archive_"& Ext)
Next

MsgBox "Archive Script is completed !",vbInformation,Title
'---------------------------------------------------------------------
REM https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/compress-archive?view=powershell-5.1&redirectedfrom=MSDN
Sub Compress_Archive(Source,Destination)
    Const ForWriting = 2
    Dim fs,Ws,ts,Ret,PSFile,ByPassPSFile
    Set fs = CreateObject("Scripting.FileSystemObject")
    Set Ws = WScript.CreateObject("WScript.Shell")
    Source = Ws.ExpandEnvironmentStrings(Source)
    Destination = Ws.ExpandEnvironmentStrings(Destination)
    PSFile = Ws.ExpandEnvironmentStrings("%Temp%") & fs.GetTempName & ".ps1"
    ByPassPSFile = "PowerShell -ExecutionPolicy bypass -noprofile -file "
    Set ts = fs.OpenTextFile(PSFile,ForWriting,True)
    ts.WriteLine "Compress-Archive -Path " & DblQuote(Source) &_
 " -Update -CompressionLevel Optimal -DestinationPath "& DblQuote(Destination)
    ts.Close
    Ret = Ws.run(ByPassPSFile & PSFile,0,True)
    If fs.FileExists(PSFile) Then fs.DeleteFile(PSFile)
End Sub
'---------------------------------------------------------------------
Function DblQuote(Str)
    DblQuote = Chr(34) & Str & Chr(34)
End Function
'---------------------------------------------------------------------

Second example : To download an image from site : Download_File.vbs

Option Explicit
Dim URL,Ws,ByPassPSFile,PSFile,MyCmd,Result
URL = "https://cdn2.unrealengine.com/Fortnite%2FBoogieDown_GIF-1f2be97208316867da7d3cf5217c2486da3c2fe6.gif"
Set Ws = CreateObject("wscript.Shell")
PSFile = Left(Wscript.ScriptFullName, InstrRev(Wscript.ScriptFullName, ".")) & "ps1"
ByPassPSFile = "cmd /C PowerShell.exe -ExecutionPolicy bypass -noprofile -file "
MyCmd = "$source = " & DblQuote(URL) & VbCrlF
MyCmd = MyCmd & "$Filename = [System.IO.Path]::GetFileName($source)" & VbCrlF
MyCmd = MyCmd & "$dest = " & DblQuote("$env:temp\$Filename") & VbCrlF
MyCmd = MyCmd & "$wc = New-Object System.Net.WebClient" & VbCrlF
MyCmd = MyCmd & "$wc.DownloadFile($source,$dest)" & VbCrlF
MyCmd = MyCmd & "Start-Process $dest"
Call WriteMyPSFile(MyCmd)
Result = Ws.run(ByPassPSFile & PSFile,0,True)
'----------------------------------------------------------------------------------------
Sub WriteMyPSFile(strText)
Dim fs,ts,PSFile
Const ForWriting = 2
    PSFile = Left(Wscript.ScriptFullName, InstrRev(Wscript.ScriptFullName, ".")) & "ps1"
    Set fs = CreateObject("Scripting.FileSystemObject")
    Set ts = fs.OpenTextFile(PSFile,ForWriting,True)
    ts.WriteLine strText
    ts.Close
End Sub
'----------------------------------------------------------------------------------------
Function DblQuote(Str)
    DblQuote = Chr(34) & Str & Chr(34)
End Function
'----------------------------------------------------------------------------------------

EDIT : 21/08/2020 @ 20:45

Here is a "pseudo-hybrid" because it use a temporary file to be executed : inspired from @vonPryz answer.

You can save it as Test.ps1 and execute from PowerShell ISE


$VBS_Content = @'
Dim http, WAN_IP
Set http = CreateObject( "MSXML2.ServerXmlHttp" )
http.Open "GET", "http://icanhazip.com", False
http.Send
WAN_IP = http.responseText
wscript.echo "WAN_IP : "  & WAN_IP
'@
Set-Content -Path $env:temp\myScript.vbs -Value $VBS_Content
& wscript.exe $env:temp\myScript.vbs

$url = "https://externals.lesechos.fr/medias/2019/04/26/2262811_pourquoi-salto-le-futur-netflix-francais-devra-seuropeaniser-195514-1.jpg" 
#https://stackguides.com/questions/35813186/extract-the-filename-from-a-path
$output = $env:temp + "\" + $url.Split("/")[-1]
$start_time = Get-Date
Try {$wb = (New-Object System.Net.WebClient).DownloadFile($url,$output)}
Catch {
    Write-Host "Error from $url ! " -ForegroundColor Red -BackgroundColor Yellow
    Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor Yellow
}
Write-Output "Running Script Time taken is : $((Get-Date).Subtract($start_time).Seconds) second(s)"
Start-process $output

Another simple example :

$VBS_Content = @'
MsgBox "This a simple MsgBox from Vbscript"
'@

$TmpVBS="$env:temp\myScript.vbs"
SC $TmpVBS $VBS_Content
wscript.exe $TmpVBS

Echo 'Hello World from Powershell !'
0
votes

Here is my final answer, I haven't tested with anything super complicated, so not sure how it would handle things like special characters...

#https://stackguides.com/questions/63514534/embed-vbscript-in-powershell-script-one-file
#######################Begin VBS1#######################
###JOB_A START###
$VBS_Content_Job_A = @'
MsgBox "This a simple MsgBox from Vbscript (Job_A)"
'@
###JOB_A END###
###JOB_B START###
$VBS_Content_Job_B = @'
MsgBox "This a simple MsgBox from Vbscript (Job_B)"
'@
###JOB_B END###
#######################Begin PS1#######################
ECHO 'Hello World from Powershell !'
PAUSE

ECHO "Running VBS Now"
PAUSE

###VBS CALL START###
$VBSJob=$VBS_Content_Job_A
$TmpVBS="$env:temp\myScript.vbs"
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
SC $TmpVBS $VBSJob 
cscript //nologo $TmpVBS
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
###VBS CALL END###

ECHO "Some More PowerShell"
PAUSE

ECHO "I need anoter VBS Script"
PAUSE

###VBS CALL START###
$VBSJob=$VBS_Content_Job_B
$TmpVBS="$env:temp\myScript.vbs"
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
Set-Content -Path $TmpVBS -Value $VBSJob
cscript //nologo $TmpVBS
Remove-Item $TmpVBS -ErrorAction SilentlyContinue
###VBS CALL END###

ECHO "All Done!"
PAUSE
-1
votes

You can embed VB.NET code into powershell code with TypeDefinition:

$code = @"
Imports System

Namespace MyNameSpace
    Public Class Responder
        Public Shared Sub StaticRespond()
            Console.WriteLine("Static Response")
        End Sub

        Public Sub Respond()
            Console.WriteLine("Instance Respond")
        End Sub
    End Class
End Namespace
"@

# Check the type has not been previously added within the session, otherwise an exception is raised
if (-not ([System.Management.Automation.PSTypeName]'MyNameSpace.Responder').Type)
{
    Add-Type -TypeDefinition $code -Language VisualBasic;
}

[MyNameSpace.Responder]::StaticRespond();

$instance = New-Object MyNameSpace.Responder;
$instance.Respond();

Not exactly vbscript but is a good solution.