Скрыть окно командной строки при использовании Exec()
Я пытаюсь выполнить этот простой тест script, но окно командной оболочки появляется после выполнения script.:
Set objShell = WScript.CreateObject("WScript.Shell")
strCommand = "cmd /C tasklist"
Set objExecObject = objShell.Exec(strCommand)
wscript.echo "Test"
Как я могу предотвратить его появление?
Обновление
Я смог улучшить его с помощью этого изменения кода:
strCommand = "cmd /C /Q tasklist"
Теперь окно отображается только в течение секунды. Но я не хочу, чтобы он вообще появлялся.
Ответы
Ответ 1
Вы всегда будете получать вспышку окна с Exec()
. Вместо этого вы можете использовать Run()
для выполнения команды в скрытом окне. Но вы не можете напрямую захватить вывод команды с помощью Run()
. Вам придется перенаправить вывод во временный файл, который ваш VBScript мог бы открыть, прочитать и удалить.
Например:
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
В классе FileSystemObject
есть методы, такие как GetSpecialFolder()
, чтобы получить путь к папке temp Windows и GetTempName()
, чтобы создать временное имя файла, которое вы можете использовать вместо жесткого кодирования имени выходного файла, как я уже говорил выше.
Также обратите внимание, что вы можете использовать аргумент /FO CSV
с tasklist.exe
для создания CSV файла, который должен упростить его синтаксический анализ.
Наконец, существуют "собственные" способы VBScript для извлечения списка запущенных процессов. Например, класс WMI Win32_Process
может сделать это без необходимости Run/Exec
.
Edit
Для полноты я должен упомянуть, что ваш script может перезапустить себя в скрытом окне консоли, где вы можете без проблем запустить Exec()
. К сожалению, это скрытое окно консоли также скроет ваш вывод из таких функций, как WScript.Echo()
. Тем не менее, вы, вероятно, не заметите никаких различий, выполняющих ваш script в разделе cscript
vs wscript
. Вот пример этого метода:
' 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
Конечно, если ваш script ожидает параметры командной строки, их нужно будет переслать при перезагрузке вашего script.
Изменить 2:
Еще одна возможность - использовать буфер обмена Windows. Вы можете передать вывод своей команды утилите clip.exe
. Затем извлеките текст через любое количество доступных COM-объектов, которые могут получить доступ к содержимому буфера обмена. Например:
' 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")
Ответ 2
Вы можете использовать .Exec()
, без флеш-памяти окна консоли, временных файлов и неожиданного отключения звука WScript.Echo
. Метод немного сложен и требует запуска вторичного связанного скрипта, поэтому я добавил комментарии:
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 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 property with signature name
Set objPrimary = objContainer.getProperty(strSignature)
' quit explorer window to release memory as it no longer needed
objContainer.Quit
' assign the secondary script me object to the primary script 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 me object reference into explorer 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
ОБНОВИТЬ
Я немного переработал код, чтобы сделать его более простым:
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
Кстати, вот реализация VBScript "многопоточность", которая использует тот же контейнерный подход.
Ответ 3
Некоторые замечательные предложения перечислены выше. Я хотел бы сделать еще одно предложение, которое является более обходным путем. Вы можете использовать Sysinternals Desktops (бесплатную программу) для запуска вашего макроса на другом рабочем столе на одном компьютере. Таким образом, мигание может произойти на собственном рабочем столе и не будет прерывать вашу работу.
Ответ 4
Я использую Sysinternals PSEXEC
https://docs.microsoft.com/sv-se/sysinternals/downloads/psexec
Создал пакетный файл (в той же папке, что и vbs и exe файл), который запускает script как системный пользователь.
Я не могу получить доступ к профилю пользователя, и мне нужно быть локальным администратором, но когда я запустил script без взаимодействия с рабочим столом, он скроет все раздражающие всплывающие окна.
Запустите script как систему без взаимодействия с рабочим столом
"%~dp0PsExec.exe" -s wscript.exe "%~dp0MyScript.vbs"
Запустите script как систему со взаимодействием с рабочим столом
"%~dp0PsExec.exe" -s -i wscript.exe "%~dp0MyScript.vbs"
Ответ 5
Чтобы скрыть окна командной строки в VBscipt, используйте Run
in WshShell
Object
Затем, чтобы получить результат, вы можете отправить этот результат в текстовый файл в %temp%
Затем прочитайте этот результат с помощью 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()
ИЛИ ЖЕ
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()
Ответ 6
Альтернатива использованию скриптового хоста Windows здесь: Запустите пакетную программу (.bat) через Visual Basic 6.0.
Он запускает программу и захватывает ее вывод на экран. У меня это работает в VB6, но не в VBA (зависает на WaitForSingleObject, не знаю почему).