wscript.sleep происходит не вовремя - PullRequest
2 голосов
/ 25 февраля 2012

Я не знаю VBScript, но в последнее время мне нужно было написать скрипт с функциональностью SendKeys, и я не смог сделать это в командном файле. Когда я запускаю следующий код из командной строки на своем компьютере под управлением Windows 7 с помощью команды cscript test.vbs, происходит сбой перед запуском telnet. Возможно, проблема в том, что команда выполнена, но полученный вывод не отображается до тех пор, пока не будет завершен спящий режим. Как бы это исправить?

Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.SendKeys "telnet localhost 25" & vbCr
wscript.sleep 5000

РЕДАКТИРОВАТЬ: я могу заставить мой сценарий работать, если я помещу команду сна перед ее запуском и затем нажму на окно командной строки, в которое он должен набрать. Таким образом, когда сценарий приостанавливается, он приостанавливает выполнение, но не приостанавливает вывод результатов в командной строке. Мне все еще нужно решение командной строки.

1 Ответ

7 голосов
/ 26 февраля 2012

Хорошо, у вашего скрипта есть два фундаментальных недостатка, которые вызывают у вас проблемы с возможным третьим. Их довольно легко пропустить, но я думаю, что они будут иметь смысл для вас, когда вы их увидите.

Сначала метод SendKeys выполняется асинхронно, что означает, что выполнение сценария не приостанавливается во время отправки нажатий клавиш. Время ожидания выполнения вашей команды должно также учитывать время, необходимое для отправки нажатий клавиш в первую очередь. Вам действительно намного лучше контролировать окно, в которое вы посылаете нажатия клавиш. Я покажу вам два способа сделать это.

Во-вторых, вы никогда не должны использовать метод SendKeys сам по себе, поскольку он не обеспечивает какого-либо способа управления тем, какое окно фактически получает отправленные нажатия клавиш. Вы всегда должны использовать его с методом AppActivate объекта Shell, который позволяет вашему сценарию сфокусировать любое открытое окно (тем самым делая его доступным для получения нажатий клавиш, которые вы собираетесь отправить).

Наконец, я не уверен, пытаетесь ли вы открыть telnet в новой командной строке или получить текущий экземпляр. Ваш сценарий подразумевает последнее, но это, вероятно, не очень хороший подход, поскольку он более подвержен ошибкам. Вам, вероятно, лучше открыть собственный экземпляр командной строки. Вы можете легко сделать это, используя метод Run объекта WshShell. Итак, на базовом уровне ваш сценарий должен выглядеть следующим образом.

' Start Telnet
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "telnet localhost 25"
' Wait for Telnet to start
WScript.Sleep 5000
' Bring the window into focus
Set objShell = CreateObject("Shell.Application")
objShell.AppActivate "telnet localhost"
' Send keystrokes to telnet window
WshShell.SendKeys "some command here"

Хотя это довольно эффективно, оно вовсе не пуленепробиваемо. Например, метод AppActivate имеет обязательный параметр, который является заголовком окна, которое нужно активировать. Это не всегда легко понять, и это может отличаться от одной машины к другой, делая этот код менее переносимым. Это также подвержено ошибкам, если одновременно открыто более одного окна одного типа. Но есть и другой способ.

Давайте вместо этого будем использовать WMI. Класс Win32_Process имеет метод Create, который позволит нам создать или запустить наш собственный процесс. Уникальность этого подхода по сравнению с нативным WSH заключается в том, что мы можем настроить некоторые свойства для нового процесса запуска. Это включает (для консольных приложений) заголовок окна. Это означает, что мы можем установить и, таким образом, точно знать заголовок окна для приложения, в которое мы хотим отправить нажатия клавиш. Вот как это выглядит.

strCommandLine = "telnet localhost 25"
strUniqueTitle = "Telnet localhost (" & WScript.ScriptName & ")"

Const SW_NORMAL = 1

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set objProcessStartup = objWMIService.Get("Win32_ProcessStartup")
Set objStartupInformation = objProcessStartup.SpawnInstance_
objStartupInformation.ShowWindow = SW_NORMAL
objStartupInformation.Title = strUniqueTitle

Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
intReturn = objProcess.Create("cmd /k" & strCommandLine, null, objStartupInformation, intProcessID)

If intReturn = 0 Then
    ' Wait for Telnet to connect
    WScript.Sleep 5000
    ' Bring the window into focus
    Set WshShell = CreateObject("WScript.Shell")
    WshShell.AppActivate strUniqueTitle
    ' Send keystrokes to telnet window
    WshShell.SendKeys "exit{ENTER}"
End If

Сценарий довольно прост для понимания, если вы пошагово выполняете его. Первым шагом является предоставление командной строки консольного приложения, которое мы хотим запустить. В этом случае телнет. Затем мы используем имя нашего скрипта для создания уникального заголовка окна для окна, которое мы собираемся открыть. Это может быть что-нибудь, что, как вы уверены, не будет соответствовать ни одному другому открытому окну.

strCommandLine = "telnet localhost 25"
strUniqueTitle = "Telnet localhost (" & WScript.ScriptName & ")"

Путь сценария начинается с настройки констант WMI и подключения к службе WMI. Мы также создаем экземпляр класса Win32_ProcessStartup, который используется в качестве объекта конфигурации, в котором находятся параметры процесса, который мы будем создавать. Здесь мы должны убедиться, что новое окно процесса запущено в видимом состоянии, и где мы устанавливаем заголовок нового окна.

Const SW_NORMAL = 1

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set objProcessStartup = objWMIService.Get("Win32_ProcessStartup")
Set objStartupInformation = objProcessStartup.SpawnInstance_
objStartupInformation.ShowWindow = SW_NORMAL
objStartupInformation.Title = strUniqueTitle

С учетом всех необходимых условий пришло время запустить наше консольное приложение. Для этого мне нужно использовать метод Create класса Win32_Process. Традиционно, сценарии WMI запрашивают службу WMI, чтобы вернуть экземпляр определенного класса. Поскольку мы создаем его, для меня не существует существующего экземпляра для запроса. Это означает, что мне нужно использовать немного другой синтаксис, чем тот, к которому вы могли бы привыкнуть. Используя метод GetObject в VBScript, я подключаюсь непосредственно к пространству имен класса Win32_Process. Со ссылкой на это пространство имен я могу вызвать метод Create и вернуть объект, который представляет вновь созданный экземпляр процесса.

Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
intReturn = objProcess.Create("cmd /k" & strCommandLine, null, objStartupInformation, intProcessID)

Метод Create, который я использовал, возвращает целое число, указывающее, был ли процесс создан успешно или нет. Поэтому, чтобы убедиться, что проблем не возникло, я завершаю оставшуюся часть кода в блок If.

If intReturn = 0 Then
    ' Wait for Telnet to connect
    WScript.Sleep 5000
    ' Bring the window into focus
    Set WshShell = CreateObject("WScript.Shell")
    WshShell.AppActivate strUniqueTitle
    ' Send keystrokes to telnet window
    WshShell.SendKeys "exit{ENTER}"
End If

Одна приятная вещь о том, как работает этот конкретный подход, состоит в том, что метод Create класса Win32_Process не выполняется асинхронно. Он не вернется, пока не будет создан новый процесс, поэтому мне не нужно беспокоиться о том, чтобы мой сценарий спал, пока это делается. Этот метод также обеспечивает постоянную ссылку WMI на запущенный процесс. Это означает, что мы можем активно следить за его состоянием, чтобы убедиться, что наш скрипт работает правильно, и чтобы убедиться, что процесс закрыт должным образом до его завершения. Я надеюсь, что это было полезно.

...