Force a VBS to run using cscript instead of wscript - vbscript

What is the stackoverflow approved (and hence correct) method to force a VBS to run using cscript instead of wscript - irrespective of what the user tries?
A quick Google search shows plenty of examples, but some of them simply don't work and those which do often don't handle the fact that it may have been run with arguments so I'm keen to know what the best way is.
Here is one example which doesn't handle arguments:
sExecutable = LCase(Mid(Wscript.FullName, InstrRev(Wscript.FullName,"\")+1))
If sExecutable <> "cscript.exe" Then
Set oShell = CreateObject("wscript.shell")
oShell.Run "cscript.exe """ & Wscript.ScriptFullName & """"
Wscript.Quit
End If
I appreciate that this could probably be easily modified to handle arguments, but realise that this may not be the best way to approach the problem.
Background: I'm writing a script which can run by double clicking or (most likely) from either a DOS batch file or as a scheduled task. It can contain one or more optional command line arguments.

My Lord, what unadulterated rubbish. It makes me cry to see such cruddy coding (no offense to anybody, lol). Seriously, though, here's my 2 pence:
Sub forceCScriptExecution
Dim Arg, Str
If Not LCase( Right( WScript.FullName, 12 ) ) = "\cscript.exe" Then
For Each Arg In WScript.Arguments
If InStr( Arg, " " ) Then Arg = """" & Arg & """"
Str = Str & " " & Arg
Next
CreateObject( "WScript.Shell" ).Run _
"cscript //nologo """ & _
WScript.ScriptFullName & _
""" " & Str
WScript.Quit
End If
End Sub
forceCScriptExecution
It handles arguments, AND checks for spaces in said arguments -- so that in the case of a filename passed to the original script instance that contained spaces, it wouldn't get "tokenized" when passed to cscript.exe.
Only thing it doesn't do is test for StdIn (e.g., in the case where someone piped something to the script via the command line, but forgot to use "cscript script.vbs") -- but if it was executed by WScript.exe, WScript.StdIn's methods all return Invalid Handle errors, so there's no way to test that anyway.
Feel free to let me know if there's a way to "break" this; I'm willing to improve it if necessary.

Two small additions to forceCScriptExecution let me see its Window after termination and handle its return code.
Sub forceCScriptExecution
Dim Arg, Str
If Not LCase( Right( WScript.FullName, 12 ) ) = "\cscript.exe" Then
For Each Arg In WScript.Arguments
If InStr( Arg, " " ) Then Arg = """" & Arg & """"
Str = Str & " " & Arg
Next
**ret =** CreateObject( "WScript.Shell" ).Run **("cmd /k** cscript //nologo """ & WScript.ScriptFullName & """ " & Str**,1,true)**
WScript.Quit **ret**
End If
End Sub
Notes: "cmd /k" let the windows stay after execution. Parameter "1" activates the window. Parameter "true" waits for termination, so variable "ret" can return the error code.

Here's a similar one in JScript for making .js files run in CScript:
(function(ws) {
if (ws.fullName.slice(-12).toLowerCase() !== '\\cscript.exe') {
var cmd = 'cscript.exe //nologo "' + ws.scriptFullName + '"';
var args = ws.arguments;
for (var i = 0, len = args.length; i < len; i++) {
var arg = args(i);
cmd += ' ' + (~arg.indexOf(' ') ? '"' + arg + '"' : arg);
}
new ActiveXObject('WScript.Shell').run(cmd);
ws.quit();
}
})(WScript);
WScript.echo('We are now in CScript. Press Enter to Quit...');
WScript.stdIn.readLine();
https://gist.github.com/4482361

One approach might be to give it another extension instead of .vbs. Say .cvbs for example. Associate .cvbs with cscript.exe not wscript.exe, that way executing or double clicking a .cvbs file will never invoke the wscript.exe.

Here is my code snippet i use for some of my scripts. It handles Arguments as well. All you have to do is replace the {EnterWorC} with either a "w" or "c" WITH quotes
Dim WorC, Command, Arguments, I
WorC={EnterWOrC} 'Make sure you replace "{EnterWOrC}" with a "w" or a "c" and BE SURE TO PUT QUOTES AROUND THE LETTER.
WorC=LCase (WorC)
If lcase (WorC)="w" Or lcase (WorC)="c" Then
If LCase (Right (WScript.FullName,11))<> WorC & "script.exe" Then
command=WScript.ScriptFullName
Arguments=""
For I=0 To UBound (WScript.Arguments)
Arguments=Arguments & Chr (34) & WScript.Arguments(I) & Chr (34) & Space (1)
Next
CreateObject("Wscript.Shell").Run WorC & "script.exe " & Chr (34) & command & Chr (34) & Space (1) & Arguments, 1
WScript.Quit
End If
WorC=Empty
Command=Empty
I=Empty
Arguments=Empty
End If
Here you will have to replace the 2nd line (2nd NON-blank line)
WorC={EnterWOrC} 'Make sure you replace "{EnterWOrC}" with a "w" or a "c" and BE SURE TO PUT QUOTES AROUND THE LETTER.
For Wscript: WorC="W"
For CScript: WorC="C"
It is NOT case Sensitive.

Related

Use VBS to run a program with parameters

I'm a total vbs novice trying to perform the supposedly simple task of using a vbscript to run a single program (with parameters).
The path the to program is:
C:\Program Files (x86)\SpeedyFox\speedyfox.exe
and the parameter switch that must go with it is:
/Firefox:C:\Program Files\Firefox\Data\profile
If I wrap both sections in quotes (due to the spaces in their paths) it gives the following combined single command:
"C:\Program Files (x86)\SpeedyFox\speedyfox.exe" "/Firefox:C:\Program Files\Firefox\Data\profile"
If I then paste this into Start > Run it works exactly as I want.
I'm just trying to achieve the same thing from a vbs script instead of manually pasting into the Run box.
I do not want the command to run within a CMD console (as other questions on here have asked). All I am trying to do is to get "C:\Program Files (x86)\SpeedyFox\speedyfox.exe" "/Firefox:C:\Program Files\Firefox\Data\profile" to work with the shell.ShellExecute line of the script below.
Set objShell = Wscript.CreateObject ("Wscript.shell")
set shell=CreateObject("Shell.Application")
shell.ShellExecute ** WHAT DO I PUT HERE? **
set shell=nothing
but try as I might, I just keep getting WSH "Expected end of statement" error messages.
1.First : I recommend you Make it a habit to use this quote function
to make it easy for you to quote variables in these situations !
2. Second : You should use MsgBox or Wscript.echo in order to show
and debug your variables easily !
Wscript.echo DblQuote("Hello World !")
Function DblQuote(Str)
DblQuote = Chr(34) & Str & Chr(34)
End Function
So, I downloaded this application (speedyfox.exe) and i tested it on my Windows 10 (32bits)
So, here is what i tested and it works like a charm on my side :
Option Explicit
Dim objShell,MyCommand,strProgramFiles,SpeedyFoxfile,Title
Title = "Execute SpeedyFox in Commandline"
Set objShell = CreateObject("Shell.Application")
strProgramFiles = GetProgramFilesPath()
SpeedyFoxfile = strProgramFiles & "\SpeedyFox\speedyfox.exe"
MsgBox "Without Double Quotes" & vbCrlf & SpeedyFoxfile,vbInformation,Title
MsgBox "With Double Quotes" & vbCrlf & DblQuote(SpeedyFoxfile),vbInformation,Title
MyCommand = "CD /D "& DblQuote(strProgramFiles &"\SpeedyFox\") &"&"& DblQuote(SpeedyFoxfile) & " " & DblQuote("/Firefox:default") & " " & DblQuote("/Chrome:Default")
MsgBox MyCommand,vbInformation,Title
Call Execute(MyCommand)
'-----------------------------------------
Function Execute(StrCmd)
Dim ws,MyCmd,Result
Set ws = CreateObject("wscript.Shell")
MyCmd = "CMD /K " & StrCmd & ""'
Result = ws.run(MyCmd,1,True)
Execute = Result
End Function
'-----------------------------------------
Function DblQuote(Str)
DblQuote = Chr(34) & Str & Chr(34)
End Function
'-----------------------------------------
Function GetProgramFilesPath()
Dim ws,OsType,strProgramFiles
Set ws = createObject("WScript.Shell")
OsType = ws.RegRead("HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE")
If OsType = "x86" then
strProgramFiles = ws.ExpandEnvironmentStrings("%PROGRAMFILES%")
elseif OsType = "AMD64" then
strProgramFiles = ws.ExpandEnvironmentStrings("%PROGRAMFILES(x86)%")
end if
GetProgramFilesPath = strProgramFiles
End Function
'-----------------------------------------
Sigh, reminds me of my vbscript days, now I use Ruby and it's just as simple as
´my_shell_command params´
However, back to your question: the shortest way to use ShellExecute is
CreateObject("Shell.Application").ShellExecute "application", "parameters", "dir", "verb", window
See this documentation for explanation of the parameters.
EDIT:
You have to pay attention at the quotes, they need to be passed to the shell also by using two quotes
eg CreateObject("Shell.Application").ShellExecute "C:\Program Files (x86)\SpeedyFox\speedyfox.exe", """/Waterfox:C:\Program Files\Waterfox\Data\profile"""

How to run a file on background using vbscript with launch options

I need to run a batch file in the background with launch option "1" (so it will %1 in the batch file).
here is my code:
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run chr(34) & "C:\Program Files\Pineapplesoft\Lost computer\lostcomputeraudio.bat" & Chr(34), 0
Set WshShell = Nothing
Use a function to quote strings, and - optionally - a sub to map all elements of an array via a manipulator function to build command lines in a structured/well scaling way; use Join() to put the parts together (with automagical space separator):
Option Explicit
Function qq(s) : qq = """" & s & """" : End Function
Sub mapF(a, f)
Dim i
For i = LBound(a) To UBound(a)
a(i) = f(a(i))
Next
End Sub
Dim sFSpec : sFSpec = "C:\Program Files\Pineapplesoft\Lost computer\lostcomputeraudio.bat"
Dim aParms : aParms = Split("1#/pi:pa po#last parm", "#")
mapF aParms, GetRef("qq")
Dim sCmd : sCmd = Join(Array( _
qq(sFSpec) _
, Join(aParms) _
))
WScript.Echo qq(sCmd)
output:
cscript startaudio.vbs
""C:\Program Files\Pineapplesoft\Lost computer\lostcomputeraudio.bat" "1" "/pi:pap po" "last parm""
The script you ask is as follows:
Set objArgs = Wscript.Arguments
Set WshShell = WScript.CreateObject("WScript.Shell")
Return = WshShell.Run("C:\Program Files\Pineapplesoft\Lost computer\lostcomputeraudio.bat " & objArgs(0), 0, false)
Save it for example as myscript.vbs.
Note that the parameter 0 in the code means that the window will be hidden. The paremeter false in the code means that the excution of the .vbs will not wait for the .bat to finish.
What will happen is that, the .vbs will start the .bat and finish its execution, leaving the .bat being executed in the background, as you request.
Exeucute it like this:
c:\<whatever>\wscript myscript.vbs <the_parameter>

Passing-variable-from-vbscript-to-batch-file with arguments

Please how to pass the 'inp" variable from this piece of vbs to my batch named job.bat? Indeed when doing echoing (echo %2) from job.bat, i notice that the inp is not passed properly. prompt command views inp and not the value retrieved from vbs. Thanks
For Each listElement In xmlDoc.selectNodes("document/Lists/list")
msgbox "toto"
inp=listElement.selectSingleNode("entry").text
out= listElement.selectSingleNode("output").text
jeton= listElement.selectSingleNode("token").text
dim shell
set shell=createobject("wscript.shell")
shell.run "job.bat ""a file"" **inp** "
set shell=nothing
Next
I think what you're looking for is this.
shell.run "job.bat ""argfile.ext"" " & inp
However, as Ansgar Wiechers points out, this is a potentially severe security hole, as a treacherously crafted XML file could run arbitrary commands. To encapsulate your batch file arguments and prevent unintended consequences, consider switching to the Shell.Application object's ShellExecute method.
For Each listElement In xmlDoc.selectNodes("document/Lists/list")
msgbox "toto"
inp = listElement.selectSingleNode("entry").text
out = listElement.selectSingleNode("output").text
jeton = listElement.selectSingleNode("token").text
set shell=CreateObject("Shell.Application")
shell.ShellExecute "job.bat", """a file"" " & inp, "path\to\batfile\", "runas", 1
set shell=nothing
Next
Unlike several other languages VBScript doesn't expand variables inside strings. Because of that, inp in the string
"job.bat ""a file"" inp "
is just the literal string "inp", not the value of the variable inp. To produce a string with the value of a variable, you have to concatenate base string and variable like #rojo suggested:
shell.run "job.bat ""a file"" " & inp
I would, however, not recommend doing this without some safety precautions. For one thing you should always put double quotes around your arguments, in case they contain spaces. I normally use a quoting function for this to prevent the instruction from becoming riddled with quad-quotes:
Function qq(str) : qq = Chr(34) & str & Chr(34) : End Function
'...
shell.run "job.bat " & qq("a file") & " " & qq(inp)
You should also always apply sanitizing to all user input that is passed to a shell command. Otherwise your users might wreak havoc by entering something like foo & del /s /q C:\*.*. Common practice is to allow only known-good characters in the input string and replace everything else with a safe character (e.g. an underscore). You can achieve this with a regular expression:
Set re = New RegExp
re.Pattern = "[^ a-z0-9äöü.,_$%()-]"
re.Global = True
re.IgnoreCase = True
inp = re.Replace(inp, "_")

How can I start an interactive console for VBS?

Very similar to this question:
How can I start an interactive console for Perl?
I just want to be able to start entering VBS statements, one at a time, and have them evaluated straight away, like Python's IDLE.
I wrote this a couple years ago. It's based on this blog post (archived here), but with a couple enhancements. Essentially it's a REPL (Read, Execute, Print, Loop) using the Execute statement:
If Not LCase( Right( WScript.FullName, 12 ) ) = "\cscript.exe" Then
For Each Arg In WScript.Arguments
If InStr( Arg, " " ) Then Arg = """" & Arg & """"
Str = Str & " " & Arg
Next
WshShell.Run "cscript """ & WScript.ScriptFullName & """" & Str, 1
WScript.Quit
End If
Do While True
WScript.StdOut.Write(">>> ")
line = Trim(WScript.StdIn.ReadLine)
If LCase(line) = "exit" Then Exit Do
On Error Resume Next
Execute line
If Err.Number <> 0 Then
WScript.StdErr.WriteLine Err.Description
End If
On Error Goto 0
Loop
I usually start it with a batch file of the same name (i.e. "vbs.vbs" and "vbs.bat"), like this:
#cscript.exe //NoLogo %~dpn0.vbs
You may try to make a debugger (cscript //X your.vbs) work for you, or to start a project of your own - perhaps based on these (first 3?) proposals

Double quotes in VBScript argument

As I read and experienced for myself VBScript removes all double quotes from and argument. Does anyone know a way around this? How to pass double quotes into the script?
If that parameter requires quotes you could use a named parameter to identify it and then enclose the value with the double quotes
dim arg
if WScript.Arguments.Named.Exists("a") then
arg = WScript.Arguments.Named("a")
arg = chr(34) & arg & chr(34)
end if
and used thus:
cscript test.vbs /a:"a parameter"
but this doesn't help if you merely want to keep quotes if supplied. Single quotes are accepted though, so you could alternatively use single quotes (or another character/string) and do a Replace(arg, "'", chr(34)) to convert to double-quotes.
This script will get the command line as it is, with double quotes and everything to a variable called strLine, and display it:
Set objSWbemServices = GetObject("WinMgmts:Root\Cimv2")
Set colProcess = objSWbemServices.ExecQuery("Select * From Win32_Process")
For Each objProcess In colProcess
If InStr (objProcess.CommandLine, WScript.ScriptName) <> 0 Then
strLine = Mid(objProcess.CommandLine, InStr(objProcess.CommandLine , WScript.ScriptName) + Len(WScript.ScriptName) + 1)
End If
Next
WScript.Echo(strLine)
So running that with:
cscript scriptname.vbs "option" ""other option"" third option
would result in:
"option" ""other option"" third option
Rather than check for a command line to include WScript.ScriptName, you can get the current PID like in https://stackoverflow.com/a/13212628/1752986
Edit: Misunderstood the question so new answer here:
I don't think you can do that in any way. However, a work around might be to use the CommandLine property of the Win32_Process class, which should get you the complete commandline I think.
For example try this script:
Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set processes = wmi.ExecQuery("SELECT * FROM Win32_Process")
For Each proc in processes
If InStr(proc.CommandLine, "double quotes") > 0 Then
wscript.echo proc.CommandLine
End IF
Next
With the parameters as: "some long commandline enclosed in double quotes here"
I am not sure if this works, but while passing parameter I guess you can do something like -
chr(34) + <your argument string> + chr(34)
The chr(34) stands for double quotes.
Unfortunately, I do not know any escape methods to pass double-quotes because its an argument delimiter. All I can suggest is to modify your script, or add the quotes from there.
Here's answer that draws upon/combines some of the others here and queries the process info based on the script's pid:
Function ArgumentsString()
Dim nPid : nPid = ThisProcessId()
For Each oPrc In GetObject("winmgmts:\\.\root\cimv2").ExecQuery(_
"Select * From Win32_Process Where ProcessId=" & nPid )
Exit For : Next
ArgumentsString = Mid( oPrc.CommandLine, _
InStr(oPrc.CommandLine, WScript.ScriptName) + _
Len(WScript.ScriptName) + 1 )
End Function
Function ThisProcessId()
ThisProcessId = 0
Dim sTFile, oPrc
With CreateObject("Scripting.FileSystemObject")
sTFile = .BuildPath(.GetSpecialFolder(2), "sleep.vbs")
With .OpenTextFile(sTFile, 2, True)
.Write "WScript.Sleep 1000"
End With
End With
With CreateObject("WScript.Shell").Exec("WScript " & sTFile)
For Each oPrc In GetObject("winmgmts:\\.\root\cimv2").ExecQuery(_
"Select * From Win32_Process Where ProcessId=" & .ProcessID)
Exit For : Next
ThisProcessId = oPrc.ParentProcessId
End With
End Function

Resources