VBscript code to capture stdout, without showing console window - vbscript

This is a VBScript code example that shows how to catch whatever a command line program sends to standard output.
It executes the command xcopy /? and shows the output in a message box. Before the message box appears, for a split second you see the console window popping up.
Set objShell = WScript.CreateObject("WScript.Shell")
Set objExec = objShell.Exec("xcopy /?")
Do
line = objExec.StdOut.ReadLine()
s = s & line & vbcrlf
Loop While Not objExec.Stdout.atEndOfStream
WScript.Echo s
Here is an other VBScript code example that shows how to execute a script without showing the console window.
objShell.Run "c:\temp\mybatch.bat C:\WINDOWS\system32\cmd.exe", 0
or
objShell.Run "c:\temp\myscript.vbs C:\WINDOWS\system32\cscript.exe", 0
As you can see it has the form <script><space><executor>.
The last example uses objShell.Run instead of objShell.Exec
What I don't know is how to execute a command line program (if necessary from a batch file), catch the standard output, without showing the console window. Any ideas?

I usually use this:
Wscript.echo execStdOut("ping google.com")
Function execStdOut(cmd)
Dim goWSH : Set goWSH = CreateObject( "WScript.Shell" )
Dim aRet: Set aRet = goWSH.exec(cmd)
execStdOut = aRet.StdOut.ReadAll()
End Function
For more advanced commands youc an wrap to comspec (cmd)
my res = execStdOut("%comspec%" & " /c " & """" & "dir /b c:\windows\*.exe" & """" & " && Echo. && Echo finished")

In order to redirect the output to the console, run the script using cscript, ex.: c:\cscript myscript.vbs.
cscript has a few command line options. The most important (to me) is the switch //NOLOGO. If yoy use it (cscript //nologo myscript.vbs) it will omit Microsoft merchandise...

This proof of concept script:
' pocBTicks.vbs - poor man's version of backticks (POC)
Option Explicit
' Globals
Const SW_SHOWMINNOACTIVE = 7
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 )
Next
Next
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
Next
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.

If you don't mind having the taskbar button appear, you can just move the console window offscreen before launching it.
If the HKCU\Console\WindowPosition key exists, Windows will use its value to position the console window. If the key doesn't exist, you'll get a system-positioned window.
So, save the original value of this key, set your own value to position it offscreen, call Exec() and capture its output, then restore the key's original value.
The WindowPosition key expects a 32-bit value. The high word is the X coordinate and the low word is the Y coordinate (XXXXYYYY).
With CreateObject("WScript.Shell")
' Save the original window position. If system-positioned, this key will not exist.
On Error Resume Next
intWindowPos = .RegRead("HKCU\Console\WindowPosition")
On Error GoTo 0
' Set Y coordinate to something crazy...
.RegWrite "HKCU\Console\WindowPosition", &H1000, "REG_DWORD"
' Run Exec() and capture output (already demonstrated by others)...
.Exec(...)
' Restore window position, if previously set. Otherwise, remove key...
If Len(intWindowPos) > 0 Then
.RegWrite "HKCU\Console\WindowPosition", intWindowPos, "REG_DWORD"
Else
.RegDelete "HKCU\Console\WindowPosition"
End If
End With
If you really want to make sure the coordinates are offscreen, you can get the screen dimensions via VBScript by using IE or other tools.

To return in VBA all subfolders in G:\OF
sub M_snb()
c00= createobejct("wscript.Shell").exec("cmd /c Dir G:\OF\*. /s/b").stdout.readall
end sub
to split the returned string into an array
sub M_snb()
sn=split(createobejct("wscript.Shell").exec("cmd /c Dir G:\OF\*. /s/b").stdout.readall,vbCrLf)
for j=0 to ubound(sn)
msgbox sn(j)
next
End Sub

This is the way you can get the command line StdOut (result) without see this popup black dos windows in vbscript:
Set Sh = CreateObject("WScript.Shell")
tFile=Sh.ExpandEnvironmentStrings("%Temp%")&"\t.txt"
Sh.Run "cmd.exe /c xcopy /? > """&tFile&""" ",0,False
Wscript.echo CreateObject("Scripting.FileSystemObject").openTextFile(tFile).readAll()

Instead of WScript.Shell, consider using using Win32_Process with startupInfo.ShowWindow = 0 to launch the process with SW_HIDE. I posted a detailed example under VBS Run cmd.exe output to a variable; not text file.

Related

VBScript program incorrectly identifying its PID?

Every time I need to grant user access to a file share on our server, I get numerous occurrences of this popup because the process hits various files whose access rights I can't modify:
This isn't a problem, but I got tired of having to repeatedly stop what I was working on to click on the "Continue" button. So, I wrote a program to continually scan for an "Error Applying Security" window, and whenever it found one, send it an "Enter" keypress. This worked well enough, but since the central loop never terminates, I decided to add the ability to end the program when it was finished. I could have used an .hta file, but I decided to try a different approach that kept everything in a single file to make future maintenance easier. I adopted the code from this Stack Overflow question to find my program's PID and allow the program to be ended using the Windows TASKKILL command after a popup was closed.
The program seems to work correctly, identifying its PID and passing it to the popup. However, when TASKKILL runs, it claims the PID doesn't exist. Can anyone see what I'm doing wrong? Thanks in advance to all who respond.
' AutoContinue.vbs
' Program to automatically close all "Error Applying Security" messages
Option Explicit
Const Hidden = 0
Dim objWshShell, objWMILocator, objWMIService
Dim strComputerName, objArgs, objChildProcess, colPIDs, objPID
Set objWshShell = CreateObject( "WScript.Shell" )
Set objWMILocator = CreateObject( "WBemScripting.SWbemLocator" )
Set objWMIService = objWMILocator.ConnectServer( "", "", "", "" )
Function MyProcessID ()
' MyProcessID finds and returns my own PID.
MyProcessID = 0
Set objChildProcess = objWshShell.Exec( "%comspec% /C pause" )
Set colPIDs= objWMIService.ExecQuery( "Select * From Win32_Process" & _
" Where ProcessId=" & objChildProcess.ProcessID,, 0 )
For Each objPID In colPIDs
MyProcessID = objPID.ParentProcessID
Next
Call objChildProcess.Terminate()
End Function
Set objArgs = WScript.Arguments
If objArgs.Count = 1 Then
If objArgs.Item(0) = "Popup" Then
objWshShell.Popup( "AutoContinue PID is " & MyProcessID & _
vbCrLf & "Hit OK when done." )
' objWshShell.Run "taskkill /PID " & MyProcessID & " /T", 1
objWshShell.Run "%comspec% /k taskkill /PID " & MyProcessID & " /T", 1
Set objArgs = Nothing
Set objWshShell = Nothing
WScript.Quit
End If
End If
objWshShell.Run "wscript.exe " & WScript.ScriptName & " Popup", Hidden
Do
Do
WScript.Sleep( 500 )
Loop While Not objWshShell.AppActivate( "Error Applying Security" )
WScript.Sleep( 100 )
objWshShell.AppActivate( "Error Applying Security" )
WScript.Sleep( 100 )
objWshShell.SendKeys( "{ENTER}" )
Loop While True
Set objWshShell = Nothing
The script identifies its own PID correctly. However, you are trying to kill itself (the script instance with supplied Popup argument) while you need to kill the script instance without it (or with another 1st argument?).
The following solution could help: supply PID of the instance to kill as 2nd argument (see variable iPid) along with the Popup one…
' AutoContinue.vbs
' Program to automatically close all "Error Applying Security" messages
Option Explicit
Const Hidden = 0
Dim objWshShell, objWMILocator, objWMIService
Dim strComputerName, objArgs, objChildProcess, colPIDs, objPID
Set objWshShell = CreateObject( "WScript.Shell" )
Set objWMILocator = CreateObject( "WBemScripting.SWbemLocator" )
Set objWMIService = objWMILocator.ConnectServer( "", "", "", "" )
Function MyProcessID ()
' MyProcessID finds and returns my own PID.
MyProcessID = 0
Set objChildProcess = objWshShell.Exec( "%comspec% /C pause" )
Set colPIDs= objWMIService.ExecQuery( "Select * From Win32_Process" & _
" Where ProcessId=" & objChildProcess.ProcessID,, 0 )
For Each objPID In colPIDs
MyProcessID = objPID.ParentProcessID
Next
Call objChildProcess.Terminate()
End Function
Dim iPid
iPid = MyProcessID()
Set objArgs = WScript.Arguments
If objArgs.Count >= 2 Then
If objArgs.Item(0) = "Popup" Then
objWshShell.Popup( "AutoContinue PID is " & objArgs.Item(1) & _
vbCrLf & "Hit OK when done." )
' objWshShell.Run "taskkill /PID " & objArgs.Item(1) & " /T /F", 1
objWshShell.Run "%comspec% /k taskkill /PID " & objArgs.Item(1) & " /T /F", 1
Set objArgs = Nothing
Set objWshShell = Nothing
WScript.Quit
End If
End If
objWshShell.Run "wscript.exe """ _
& WScript.ScriptFullName & """ Popup " & CStr( iPid) , Hidden
'' ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ two arguments
Do
Do
WScript.Sleep( 500 )
Loop While Not objWshShell.AppActivate( "Error Applying Security" )
WScript.Sleep( 100 )
objWshShell.AppActivate( "Error Applying Security" )
WScript.Sleep( 100 )
objWshShell.SendKeys( "{ENTER}" )
Loop While True

How FileSystemObject count property handles the list of files return by the dir command

Function Run_Cmd(strCmd)
Dim objShell
Dim objScriptExec
Dim strCmdResult
strCmd = "%comspec% /C " + strCmd
Set objShell = CreateObject("WScript.Shell")
Set objScriptExec = objShell.exec(strCmd)
set Run_Cmd = objScriptExec.StdOut
End Function
Function test ()
'...
Set objPropFilesList = Run_Cmd("dir /B " & sStarterDir & " | findstr /I """&test_list&"""")
if ( objPropFilesList.count = 0 ) Then
LogWrite "No EPM services found to verify... Aborting execution.", fAutoFixLog, bLogToConsole
wscript.echo "No EPM services found to verify... Aborting execution."
Exit Function
End If
Do Until objPropFilesList.AtEndOfStream
'...
Loop
End Function
In the above code,objPropFilesList returns text stream. When i have placed if condition to check the count, it skips the remaining code in this function. I doesn't understand why it skipping this code.
My suspection is that, as per below doc Count Property handles Dictionary object. The objPropFilesList returns the list of file names, does this will not be considered as dictionary object.
https://msdn.microsoft.com/en-us/library/ea5ht6ax(v=vs.84).aspx
I want to understand what exactly happening here.
You are running a console command and capturing the output in a textstream. It is just text. You can convert to an array of lines. A = Split(A, vbcrlf).
This shows how to do file operations in VBScript. This uses recursion to give a dir /s type operation.
'On Error Resume Next
Set fso = CreateObject("Scripting.FileSystemObject")
Dirname = InputBox("Enter Dir name")
ProcessFolder DirName
Sub ProcessFolder(FolderPath)
On Error Resume Next
Set fldr = fso.GetFolder(FolderPath)
Set Fls = fldr.files
For Each thing in Fls
msgbox Thing.Name & " " & Thing.path
Next
Set fldrs = fldr.subfolders
For Each thing in fldrs
msgbox Thing.Name & " " & Thing.path
ProcessFolder thing.path
Next
End Sub
To demonstrate:
The error caused by accessing the non-existing .Count property was hidden by an EVIL global OERN ((c) Lankymart)
You can easily use .AtEndOfStream to determine whether you need to log a failure instead of looping over the results.
You really can't use Split on the TextStream.
wtf
Option Explicit
Function Run_Cmd(ByVal strCmd) ' nasty surprises without ByVal as you change strCmd in the function
' Dim strCmdResult - not used; proves that 'long' Dim lists 'far' from the use of the variables are a bad idea
strCmd = "%comspec% /C " + strCmd
Set Run_Cmd = CreateObject("WScript.Shell").exec(strCmd).StdOut
End Function
Dim sPat : sPat = "vbs"
If 1 <= WScript.Arguments.Count Then sPat = WScript.Arguments(0)
Dim tsCmd : Set tsCmd = Run_Cmd("dir /B .\* | findstr /I """ & sPat & """")
WScript.Echo TypeName(tsCmd)
' OERN *hides* errors
On Error Resume Next
tsCmd = Split(tsCmd, vbCrLf)
WScript.Echo Err.Number, Err.Description
On Error GoTo 0
' use tsCmd.AtEndOfStream to determine whether to log before you loop over the lines
If tsCmd.AtEndOfStream Then
WScript.Echo "Log: No EPM"
Else
Do Until tsCmd.AtEndOfStream
WScript.Echo tsCmd.ReadLine()
Loop
End If
output:
cscript 41887615.vbs
TextStream
438 Das Objekt unterstützt diese Eigenschaft oder Methode nicht.
41887615.vbs
cscript 41887615.vbs pipapo
TextStream
438 Das Objekt unterstützt diese Eigenschaft oder Methode nicht.
Log: No EPM

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>

VBS Run cmd.exe output to a variable; not text file

This is what I have so far. It works; outputing the folder path to temp to a text file.
What I really want, is to output the data to a variable. Every example I see online, show how to do this using something like:
set objScriptExec = wshShell.Exec (strCommand)
followed by
strresult = LCase(objScriptExec.StdOut.ReadAll. // code
I want this to run with Run, not Exec, because I want the command prompt windows to be hidden as I will performing many commands with the code below. How can I capture that output to a variable?
Set wsShell = CreateObject("WScript.Shell")
strCommand = "cmd /c echo %temp% > %temp%\test.txt"
wsShell.Run strcommand,0,True
This may be done with the Windows Script Host Exec command. StdOut, StdIn, and StdErr may all be accessed, and ERRORLEVEL is available when the command completes.
Dim strMessage, strScript, strStdErr, strStdOut
Dim oExec, oWshShell, intErrorLevel
Dim ComSpec
Set oWshShell = CreateObject("WScript.Shell")
ComSpec = oWshShell.ExpandEnvironmentStrings("%comspec%")
intErrorLevel = 0
strScript = ComSpec & " /C echo %temp%"
On Error Resume Next
Set oExec = oWshShell.Exec (strScript)
If (Err.Number <> 0) Then
strMessage = "Error: " & Err.Message
intErrorLevel = 1
Else
Do While oExec.Status = 0
Do While Not oExec.StdOut.AtEndOfStream
strStdOut = strStdOut & oExec.StdOut.ReadLine & vbCrLf
Loop
Do While Not oExec.StdErr.AtEndOfStream
strStdErr = strStdErr & oExec.StdErr.ReadLine & vbCrLf
Loop
WScript.Sleep 0
Loop
intErrorLevel = oExec.ExitCode
strMessage = strStdOut & strStdErr & CStr(intErrorLevel)
End If
WScript.Echo (strMessage)
NOTE: Replacing "ReadLine" above with "Read(1)" accomplishes the same thing, but adds an ability to process characters rather than whole lines.
Of course Wscript.Shell would be a lot easier, but, since you want more fine grain control of your session, consider using Win32_Process. Usually, one uses this to control the placement of a new window, but, in your case, you want it hidden, so I set startupInfo.ShowWindow = 0 which means SW_HIDE. The following declares a VBScript function called RunCmd and which will run a command in an invisible window saving the output to a text file and then return the contents of the text file to the caller. As an example, I invoke RunCmd with the HOSTNAME command:
Function RunCmd(strCmd)
Dim wmiService
Set wmiService = GetObject("winmgmts:\\.\root\cimv2")
Dim startupInfo
Set startupInfo = wmiService.Get("Win32_ProcessStartup")
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")
Dim cwd
cwd = fso.GetAbsolutePathname(".")
startupInfo.SpawnInstance_
startupInfo.ShowWindow = 0
' startupInfo.X = 50
' startupInfo.y = 50
' startupInfo.XSize = 150
' startupInfo.YSize = 50
' startupInfo.Title = "Hello"
' startupInfo.XCountChars = 36
' startupInfo.YCountChars = 1
Dim objNewProcess
Set objNewProcess = wmiService.Get("Win32_Process")
Dim intPID
Dim errRtn
errRtn = objNewProcess.Create("cmd.exe /c """ & strCmd & """ > out.txt", cwd, startupInfo, intPID)
Dim f
Set f = fso.OpenTextFile("out.txt", 1)
RunCmd = f.ReadAll
f.Close
End Function
MsgBox RunCmd("HOSTNAME")
References:
Create method of the Win32_Process class
Win32_ProcessStartup class

Show Message dialog while executing

I use this snippet in vbscript:
Set WSH = CreateObject("WScript.Shell")
cmd = "some command"
flag = WSH.Run(cmd, 0, true)
As it can be noticed, in .Run() call, "WaitOnReturn" is set to "true" as I want to know when external program finishes and additionally it status
Problem is that external program needs some time to finish and I want to pop "Please wait..." MsgBox but I can't this way as I set "WaitOnReturn" on "true" which I need as I need result from that program for additional processing
Is there a way I can show somehow this MsgBox while external program is executed?
Sorry, it slipped to me that i can call MsgBox just before executing, Run()
:embarrassed:
Edit:
for no user interaction here is one workaround (taken from http://www.robvanderwoude.com/vbstech_ui_progress.php)
Function ProgressMsg( strMessage, strWindowTitle )
' Written by Denis St-Pierre
Set wshShell = WScript.CreateObject( "WScript.Shell" )
strTEMP = wshShell.ExpandEnvironmentStrings( "%TEMP%" )
If strMessage = "" Then
On Error Resume Next
objProgressMsg.Terminate( )
On Error Goto 0
Exit Function
End If
Set objFSO = CreateObject("Scripting.FileSystemObject")
strTempVBS = strTEMP + "\" & "Message.vbs"
Set objTempMessage = objFSO.CreateTextFile( strTempVBS, True )
objTempMessage.WriteLine( "MsgBox""" & strMessage & """, 4096, """ & strWindowTitle & """" )
objTempMessage.Close
On Error Resume Next
objProgressMsg.Terminate( )
On Error Goto 0
Set objProgressMsg = WshShell.Exec( "%windir%\system32\wscript.exe " & strTempVBS )
Set wshShell = Nothing
Set objFSO = Nothing
End Function
Then call it with:
ProgressMsg "Installing, Please wait.", "Some title"
end terminate it with:
ProgressMsg "", "Some title"
I was given an answer on another blog, basically, all I had to do was dim the variable "ProgressMsg" globally.
Thanks

Resources