Black
Black

Reputation: 20302

Hide command prompt window when using Exec()

I'm trying to execute this simple test script, but a command shell window is appearing after I execute the script.:

Set objShell = WScript.CreateObject("WScript.Shell")

strCommand = "cmd /C tasklist"

Set objExecObject = objShell.Exec(strCommand)

wscript.echo "Test"

How can I prevent it from showing up?

Update

I was able to improve it with this code change:

strCommand = "cmd /C /Q tasklist"

Now the window only shows up for a split second. But I don't want it to show up at all.

Upvotes: 27

Views: 93327

Answers (7)

TungSon Nguyen
TungSon Nguyen

Reputation: 1

My solution is:

sExecString = 'your execute command here
CreateObject("Wscript.Shell").Run sExecString, vbHide, True

Upvotes: 0

G. Ciardini
G. Ciardini

Reputation: 1307

After trying the main solutions without success, I was able to solve my problem with the following code:

With CreateObject("WScript.Shell")

    .Run "cmd /c start /b tasklist.exe > c:\out.txt", 0, True

End With

The real deal was the "/b" as the console help display:

START ["title"] [/D path] [/I] [/MIN] [/MAX] [/SEPARATE | /SHARED]
      [/LOW | /NORMAL | /HIGH | /REALTIME | /ABOVENORMAL | /BELOWNORMAL]
      [/NODE <NUMA node>] [/AFFINITY <hex affinity mask>] [/WAIT] [/B]
      [command/program] [parameters]

    "title"     Title to display in window title bar.
    path        Starting directory.
    B           Start application without creating a new window. The
                application has ^C handling ignored. Unless the application
                enables ^C processing, ^Break is the only way to interrupt
                the application.

Upvotes: 0

hollopost
hollopost

Reputation: 597

To hide the command line windows in VBscipt is use Run in WshShell Object

Then to get the result you can send this result to text file in %temp%

Then read this result with FileSystemObject

Set Sh = CreateObject("WScript.Shell")
tFile=Sh.ExpandEnvironmentStrings("%Temp%")&"\t.txt"
Sh.Run "cmd.exe /C tasklist > """&tFile&""" ",0,False
Wscript.echo CreateObject("Scripting.FileSystemObject").openTextFile(tFile).readAll() 

OR

If StrComp(right(WScript.FullName,11),"wscript.exe",1) = 0 Then     '' get only wscript.exe from "c:\windows\system32\wscript.exe" to compere with wscript.exe
  WScript.Quit CreateObject("WScript.Shell").Run("cscript.exe """ & WScript.ScriptFullName & """", 0, False)
End If
MsgBox CreateObject("WScript.Shell").Exec("cmd.exe /c tasklist /v /fi ""imagename EQ explorer*"" /FO LIST | FIND ""PID:""").StdOut.ReadAll()

Upvotes: 1

omegastripes
omegastripes

Reputation: 12612

You can use .Exec() method, without console window flash, temp files and unexpected WScript.Echo output muting. The method is slightly tricky, and requires to launch secondary linked script, so I added the comments:

Option Explicit

Dim objDummy, strSignature, objPrimary, objSecondary, objContainer, objWshShell, objWshShellExec, strResult

' this block is executed only in the secondary script flow, after primary script runs cscript
If WScript.Arguments.Named.Exists("signature") Then
    ' retrieve signature string from argument
    strSignature = WScript.Arguments.Named("signature")
    Do
        ' loop through all explorer windows
        For Each objContainer In CreateObject("Shell.Application").Windows
            ' check if the explorer's property with signature name contains the reference to the live script
            If ChkVBScriptTypeInfo(objContainer.getProperty(strSignature)) Then
                Exit Do
            End If
        Next
        WScript.Sleep 10
    Loop
    ' create shell object within secondary script
    Set objWshShell = CreateObject("WScript.Shell")
    ' retrieve the primary script me object reference from explorer's property with signature name
    Set objPrimary = objContainer.getProperty(strSignature)
    ' quit explorer window to release memory as it's no longer needed
    objContainer.Quit
    ' assign the secondary script me object to the primary script's variable
    Set objPrimary.objSecondary = Me
    ' emtpy loop while primary script is working
    Do While ChkVBScriptTypeInfo(objPrimary)
        WScript.Sleep 10
    Loop
    ' terminate secondary
    WScript.Quit
End If

' the code below is executed first in the primary script flow 
' create signature string
strSignature = Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
' create new hidden explorer window as container to transfer a reference between script processes
Set objContainer = GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
' put this script's me object reference into explorer's property
objContainer.putProperty strSignature, Me
' launch new secondary process of the same script file via cscript.exe with hidden console window, providing signature string in named argument to identify host script
CreateObject("WScript.Shell").Run ("""" & Replace(LCase(WScript.FullName), "wscript", "cscript") & """ //nologo """ & WScript.ScriptFullName & """ ""/signature:" & strSignature & """"), 0
' wait until secondary script has been initialized and put his me object into this script variable
Do Until ChkVBScriptTypeInfo(objSecondary)
    WScript.Sleep 10
Loop

' here is your code starts...
' create exec object within hidden console window of secondary script, execute cmd instruction
Set objWshShellExec = objSecondary.objWshShell.Exec("%comspec% /c tasklist")
' read cmd output
strResult = objWshShellExec.StdOut.ReadAll()
WScript.Echo strResult
' ...


' utility check if me object is live
Function ChkVBScriptTypeInfo(objSample)
    On Error Resume Next
    If TypeName(objSample) <> "VBScriptTypeInfo" Then
        ChkVBScriptTypeInfo = False
        Exit Function
    End If
    ChkVBScriptTypeInfo = True
End Function

UPDATE

I've slightly reworked the code to make it more straightforward:

Option Explicit

Dim strCmd, strRes, objWnd, objParent, strSignature

If WScript.Arguments.Named.Exists("signature") Then WshShellExecCmd
strCmd = "%comspec% /c tasklist"
RunCScriptHidden
WScript.Echo strRes

Sub RunCScriptHidden()
    strSignature = Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
    GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}").putProperty strSignature, Me
    CreateObject("WScript.Shell").Run ("""" & Replace(LCase(WScript.FullName), "wscript", "cscript") & """ //nologo """ & WScript.ScriptFullName & """ ""/signature:" & strSignature & """"), 0, True
End Sub

Sub WshShellExecCmd()
    For Each objWnd In CreateObject("Shell.Application").Windows
        If IsObject(objWnd.getProperty(WScript.Arguments.Named("signature"))) Then Exit For
    Next
    Set objParent = objWnd.getProperty(WScript.Arguments.Named("signature"))
    objWnd.Quit
    objParent.strRes = CreateObject("WScript.Shell").Exec(objParent.strCmd).StdOut.ReadAll()
    WScript.Quit
End Sub

BTW, here is VBScript "multithreading" implementation that uses the same container approach.

Upvotes: 14

Henrik Andersen
Henrik Andersen

Reputation: 11

I use Sysinternals PSEXEC https://learn.microsoft.com/sv-se/sysinternals/downloads/psexec

Created a Batch-file (in the same folder as the vbs and exe-file) that runs the script as system user. I can not Access the user profile and I need to be local Admin but when i run the script without interaction with the desktop it will hide all annoying popups.

Run Script as system without interaction with desktop

"%~dp0PsExec.exe" -s wscript.exe "%~dp0MyScript.vbs"

Run Script as system with interaction with desktop

"%~dp0PsExec.exe" -s -i wscript.exe "%~dp0MyScript.vbs"

Upvotes: 1

Bond
Bond

Reputation: 16311

You're always going to get a window flash with Exec(). You can use Run() instead to execute the command in a hidden window. But you can't directly capture the command's output with Run(). You'd have to redirect the output to a temporary file that your VBScript could then open, read, and delete.

For example:

With CreateObject("WScript.Shell")

    ' Pass 0 as the second parameter to hide the window...
    .Run "cmd /c tasklist.exe > c:\out.txt", 0, True

End With

' Read the output and remove the file when done...
Dim strOutput
With CreateObject("Scripting.FileSystemObject")

    strOutput = .OpenTextFile("c:\out.txt").ReadAll()
    .DeleteFile "c:\out.txt"

End With

The FileSystemObject class has methods like GetSpecialFolder() to retrieve the path of Windows temp folder and GetTempName() to generate a temporary filename that you can use instead of hardcoding an output filename as I've done above.

Also note that you can use the /FO CSV argument with tasklist.exe to create a CSV file which should make parsing it much easier.

Finally, there are VBScript "native" ways to retrieve the list of running processes. WMI's Win32_Process class, for example, can do this without the need for Run/Exec.


Edit:

For the sake of completeness, I should mention that your script can relaunch itself in a hidden console window where you can run Exec() silently. Unfortunately, this hidden console window will also hide your output from functions like WScript.Echo(). Aside from that, however, you probably won't notice any differences running your script under cscript vs wscript. Here's an example of this method:

' If running under wscript.exe, relaunch under cscript.exe in a hidden window...
If InStr(1, WScript.FullName, "wscript.exe", vbTextCompare) > 0 Then
    With CreateObject("WScript.Shell")
        WScript.Quit .Run("cscript.exe """ & WScript.ScriptFullName & """", 0, True)
    End With
End If

' "Real" start of script. We can run Exec() hidden now...
Dim strOutput
strOutput = CreateObject("WScript.Shell").Exec("tasklist.exe").StdOut.ReadAll()

' Need to use MsgBox() since WScript.Echo() is sent to hidden console window...
MsgBox strOutput  

Of course, if your script expects command-line parameters, those would need to be forwarded when relaunching your script as well.


Edit 2:

Yet another possibility is to use the Windows clipboard. You can pipe the output of your command to the clip.exe utility. Then, retrieve the text via any number of available COM objects that can access the contents of the clipboard. For example:

' Using a hidden window, pipe the output of the command to the CLIP.EXE utility...
CreateObject("WScript.Shell").Run "cmd /c tasklist.exe | clip", 0, True

' Now read the clipboard text...
Dim strOutput
strOutput = CreateObject("htmlfile").ParentWindow.ClipboardData.GetData("text")

Upvotes: 50

Fidel
Fidel

Reputation: 7397

Some great suggestions are listed above. I'd like to make one more suggestion which is more of a workaround. You can use Sysinternals Desktops (a free program) to run your macro on another desktop on your same machine. That way the flashing can all happen on its own desktop and won't interrupt your work.

Upvotes: 2

Related Questions