С учетом ряда ограничений, для запуска программ на локальных и удаленных компьютерах можно использовать и возможности интерфейса Windows Management Instrumentation (WMI). Преимущества и недостатки данного подхода перечислены в Таблице 1. В настоящей публикации будет представлен сценарий, в котором запуск программ на локальных либо удаленных компьютерах реализуется через интерфейс WMI. В качестве дополнительных возможностей этого сценария можно отметить ожидание завершения программы и отображение времени ее выполнения.

Команда для запуска сценария RunProgram.vbs

Сценарий RunProgram.vbs использует для удаленного или локального запуска программ объекты WMI Win32_ProcessStartup и Win32_Process. Сценарий может работать на компьютерах с Windows 2000 с установленным на них компонентом Windows Script Host (WSH) 5.6, а также на компьютерах с Windows XP и последующих версий (начиная с Windows XP, компонент WSH 5.6 входит в состав операционной системы). Синтаксис команды запуска выглядит следующим образом:

RunProgram.vbs "command"
[/startin:path]
[/window:n]
[/computer:computer
[/username:username]
[/password:password]]
[/wait [/elapsed]]

В параметре command указывается командная строка, предназначенная для запуска. Не забывайте заключать ее в двойные кавычки (""). Команда запуска должна начинаться с имени исполняемого файла, поэтому если вы хотите запустить одну из внутренних команд cmd.exe (скажем, Dir или Copy) или какой-либо сценарий командной строки для оболочки cmd.exe, то в этом случае команда запуска должна начинаться с cmd /c. Если же нужно запустить сценарий WSH, то команда запуска должна начинаться с имени сервера сценариев cscript или wscript.

С помощью параметра /startin:path можно указать путь к каталогу, в котором будет выполняться запускаемая программа. Если этот параметр не указан, программа запустится в том же каталоге, что и вызвавший ее процесс (обычно это каталог %SystemRoot%system32). Если в описании пути содержатся пробелы, его необходимо заключать в двойные кавычки ("").

Параметр /window:n используется для настройки начального состояния окна, в котором будет выполняться программа. Это цифровой параметр, которому присваиваются те же значения, что и параметру intWindowStyle метода Run объекта WshShell. Например, если задано значение /window:7, программа будет запускаться в свернутом окне. По умолчанию используется значение /window:1 (что соответствует стандартному отображению окна на экране). Список всех допустимых значений данного параметра можно найти по адресу http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/wsmthrun.asp. Из соображений безопасности программы, выполняемые на удаленных компьютерах с Windows 2000 Service Pack 3 (SP3) и последующих версий, всегда выполняются в скрытом окне.

В параметре /computer:computer указывается имя удаленного компьютера, на котором должна выполниться запускаемая программа. Если этот параметр не задан, команда будет выполняться на локальном компьютере. С помощью ключей /user:username и /password:password можно задавать альтернативные параметры учетной записи для подключения к удаленной системе. Если эти параметры не заданы, сценарий RunProgram.vbs будет использовать текущую учетную запись, под которой вы зарегистрировались в системе. Для указания обоих параметров учетной записи необходимо, чтобы был задан ключ /computer, поскольку WMI не позволяет указывать параметры учетной записи для локального подключения. Здесь необходимо учитывать, что при использовании аргументов, задающих параметры учетной записи, и имя пользователя, и его пароль будут отображаться в командной строке в явном виде, поэтому данную возможность следует использовать аккуратно.

Ключ /wait позволяет указать сценарию, что необходимо ожидать завершения запущенной программы, а ключ /elapsed может использоваться для вывода на экран сведений о времени ее выполнения. Параметр /elapsed может использоваться только совместно с ключом /wait.

Если программа должна запускаться на удаленном компьютере (или на локальном, но в скрытом окне), то при этом важно, чтобы она могла выполняться без вмешательства пользователя, поскольку в данном случае она для него невидима. Также необходимо учитывать, что при запуске программы на удаленном компьютере она не будет иметь доступа к сети (из соображений безопасности).

При запуске программы через RunProgram.vbs на экране отображается некоторая начальная информация: имя компьютера, на котором была запущена команда, выполняемая командная строка, каталог запуска программы (если он был задан) и идентификатор процесса (process ID), соответствующего запущенной программе (уникальный номер, который присвоен данному процессу). Типовой формат выходных данных сценария показан на Рисунке 1. В том случае, если указаны параметры /wait и /elapsed, по окончании выполнения программы на экране будут отображены данные о моменте ее запуска и завершения, а также время выполнения.

Процедура Main

В начале сценария объявляются две константы: SCRIPT_NAME, используемая процедурой ShowUsage, и PROCESS_CHECK_INTERVAL, которая используется в тех случаях, когда сценарий ожидает завершения выполнения программы. Затем запускается процедура Main, текст которой приведен в Листинге 1.

В начале этой процедуры объявляются необходимые переменные, после чего с помощью переменной Args формируется ссылка на объект WScript.Arguments. Если коллекция Unnamed пуста (т.е. ее свойство Count равно 0), либо в строке запуска был выбран ключ /?, вызывается процедура ShowUsage, которая выводит на экран краткое сообщение о корректном синтаксисе вызова сценария и завершает его выполнение.

Затем процедура Main считывает первый неименованный аргумент командной строки (т.е. первый аргумент, который не начинается с символа прямого слеша (/)) и вызывает функцию VBScript Unescape, которая заменяет последовательности символов %xx на их эквиваленты в коде ASCII (см. Листинг 1, фрагмент с меткой A). Функция Unescape предоставляет механизм внедрения символов двойных кавычек в строку, заключенную в кавычки. Более подробно это обсуждается во врезке "Добавление двойных кавычек в строку команды".

Далее процедура Main считывает данные о каталоге запуска (/startin), состоянии окна (/window) имени пользователя (/username) и его пароле (/password) и сохраняет эти сведения в соответствующих переменных. Для этого процедура обращается к коллекции WScript.Arguments.Named, содержащей те аргументы командной строки, которые начинаются с символа прямого слеша (/). Если в командной строке такой аргумент не задан, то при обращении к содержимому будет возвращено специальное значение Empty. В VBScript при сравнении Empty с пустой строкой ("") возвращается значение True, поэтому сравнение значения аргумента параметра командной строки с пустой строкой является простым способом определения, имеет ли тот или иной ключ командной строки аргумент (например, ключ /startin:C: имеет аргумент C:, а ключ /startin не имеет аргумента).

После этого процедура Main проверяет, заданы ли в командной строке параметры /wait и /elapsed. Если они заданы, то в соответствующие переменные (Wait и Elapsed) записываются значения True.

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

Подключение к компьютеру, имя которого было указано в командной строке (или к локальному, если ключ /computer не задан), осуществляется с помощью метода ConnectServer объекта WMIExec (имя указывается после названия используемого метода). Если метод возвращает ненулевой код завершения, процедура Main выводит сообщение об ошибке и завершает сценарий с кодом ошибки, возвращенным данным методом. Обычно эти коды ошибки начинаются с 0x8007 (что означает ошибку исполнения), а заканчиваются четырьмя шестнадцатеричными цифрами, являющимися ссылкой на соответствующую ошибку Win32. Чтобы интерпретировать ошибку, нужно преобразовать эти последние четыре цифры к десятичному виду, после чего запустить в командной строке команду Net Helpmsg, указав полученное десятичное значение в качестве параметра. Например, если был возвращен код ошибки 0x800706BA, то после преобразований получим 1722. Теперь, если запустить команду

Net Helpmsg 1722

то на экране отобразится сообщение The RPC server is unavailable (это говорит о том, что выбранный компьютер недоступен по сети).

Если метод ConnectServer выполнился успешно, то процедура Main продолжает выполнение. Если в командной строке был указан ключ /elapsed (т.е. если переменная Elapsed имеет значение True), то с помощью функции VBScript Now извлекается значение текущей даты и времени, которое записывается в переменную StartTime.

Затем Main вызывает метод RunProgram объекта WMIExec, с помощью которого осуществляется запуск программы. В случае успешного выполнения метод возвращает нулевое значение. В противном случае будет возвращено одно из значений, описанных в документе Microsoft "Create Method of the Win32_Process Class", который доступен по адресу http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/create_method_in_class_win32_process.asp,  после чего сценарий завершает свое выполнение. При успешном выполнении метода RunProgram сценарий выводит строку, возвращаемую методом Status объекта WMIExec.

Если в команде запуска сценария был задан ключ /wait, тогда последней задачей процедуры Main является приостановка выполнения сценария до тех пор, пока функция ProcessExists объекта WMIExec не возвратит значение True. Это реализуется с помощью цикла Do...Loop, внутри которого вызывается метод Sleep объекта WScript, приостанавливающий выполнение сценария на интервал времени (в миллисекундах), определяемый константой PROCESS_CHECK_INTERVAL, значение которой по умолчанию равно 500 (полсекунды). Если интервал слишком мал и это приводит к излишнему количеству используемых циклов ЦПУ, данное значение может быть увеличено.

Если в команде запуска сценария присутствует ключ /elapsed, то процедура Main с помощью повторного вызова функции VBScript Now извлекает значение текущей даты и времени, которое фиксируется в переменной EndTime. Далее формируется строка выходных данных с именем TimeInfo, которая содержит значения времени запуска (start time), времени завершения (end time) и времени выполнения программы (elapsed time). Чтобы получить значение времени выполнения, процедура вызывает функцию VBScript DateDiff, с помощью которой вычисляется разность (в секундах) между временем запуска и временем завершения. Это значение передается в качестве параметра функции GetElapsed, которая будет рассмотрена ниже.

Если сценарий был запущен через сервер Wscript, то окончательный результат его выполнения будет содержать как сведения о процессе, полученные с помощью метода Status объекта WMIExec, так и информацию о времени. Если сценарий запущен через CScript, то в выходных данных будет представлена только информация о времени. В этом случае сценарию не требуется выводить информацию о процессе, поскольку он уже находится в окне командной строки.

Класс WMIExec

Применяемая в данном сценарии функциональность WMI реализована в виде отдельного класса, поскольку данный класс использует набор из пяти переменных, которые должны совместно использоваться методами. Если бы сценарий был разработан без применения отдельного класса, это привело бы либо к необходимости использования глобальных переменных, либо к необходимости передачи большого количества параметров вспомогательным функциям. В результате сценарий получился бы довольно громоздким и сложным для чтения и эксплуатации. Чтобы избежать ошибок и конфликтов имен с другими переменными, используемых методами класса, в названии этих пяти переменных был введен префикс c_. Ранее уже было рассмотрено, как процедура Main вызывает четыре метода класса WMIExec, однако я хочу привести некоторые дополнительные сведения о том, как работают эти методы:

Метод ConnectServer. Данный метод создает объект WMI SWbemLocator, для чего используется функция VBScript CreateObject. При этом задается свойство Security_ данного объекта (как это было бы в случае объекта SWbemSecurity) путем присвоения его свойству ImpersonationLevel значения 3 (т.е., wbemImpersonationLevelImpersonate). Далее с помощью конструкции On Error Resume Next отключается встроенный обработчик ошибок VBScript. После этого вызывается метод ConnectServer объекта SWbemLocator. Значение, возвращаемое методом ConnectServer объекта WMIExec, соответствует значению свойства Number объекта Err, которое при успешном установлении соединения будет равно нулю. Если соединение установлено успешно, то переменная c_SWbemServices будет содержать ссылку на объект SWbemServices. При вызове метода ConnectServer объекта WMIExec используется объект SWbemLocator, а не моникер WMI (например, winmgmts: ...), поскольку внутри строки моникера нет возможности задавать параметры учетной записи для установки соединения.

Метод RunProgram. Метод RunProgram использует объект SWbemServices, созданный при помощи метода ConnectServer объекта WMIExec, для создания объекта Win32_ProcessStartup и конфигурирует свойство ShowWindow объекта Win32_ProcessStartup. Далее, путем повторного обращения к объекту SWbemServices, он создает объект Win32_Process, использует его метод Create для запуска программы и завершает выполнение с результатом, возвращаемым методом Create.

Метод ProcessExists. С помощью этого метода определяется, существует ли данный процесс, для чего используется объект SWbemServices, созданный при помощи метода ConnectServer объекта WMIExec, а также идентификатор процесса process ID, созданный методом RunProgram. Для этого выполняется запрос к WMI для получения соответствующего process ID, считывается значение свойства Count коллекции, и если число, соответствующее количеству экземпляров, больше нуля, то возвращается значение True.

Метод Status. Метод Status использует переменные класса computer name (имя компьютера), command line (строка команды) и starting directory (каталог запуска) и возвращает строку в формате, показанном на Рисунке 1.

Функция GetElapsed

Имеющаяся в сценарии RunProgram.vbs функция GetElapsed служит для определения времени в часах. Для этого с помощью оператора целочисленного деления языка VBScript () количество секунд делится на 3600 (что соответствует количеству секунд в одном часе). Если результат больше нуля, функция вычитает количество часов. Далее оставшееся количество секунд делится на 60, чтобы получить количество минут, после чего количество минут также вычитается. Полученное значение времени представляется в виде строки n hour(s) (часы), n minute(s) (минуты), n second(s) (секунды).

Компонент WMI является мощным средством в арсенале сценариев для системного администрирования, и сценарий RunProgram.vbs демонстрирует еще один из возможных случаев применения данного инструмента. Возьмите на вооружение данный сценарий, и в вашем распоряжении появится еще один вариант запуска программ на удаленных компьютерах.


Рисунок 1. Формат вывода сценария

RunProgram.vbs Computer: или (LocalSystem)Command line: Starting directory: или (Default) Process ID:


Листинг 1. Сценарий RunProgram.vbs (процедура Main)

Sub Main()
Dim Args
Dim Command, StartIn, Computer, UserName, Password
Dim Wait, Elapsed
Dim ShowWindow, Result
Dim StartTime, EndTime
Dim Output, TimeInfo
Dim WMIRun

Set Args = WScript.Arguments

' Если не именованные аргументы не заданы или указан аргумент /?
' выводится справка о корректной строке запуска
If (Args.Unnamed.Count = 0) Or Args.Named.Exists("?") Then _
ShowUsage

' BEGIN CALLOUT A
Command = Unescape(Args.Unnamed(0))
' END CALLOUT A

StartIn = Args.Named("startin")

ShowWindow = Args.Named("window")
If Not IsNumeric(ShowWindow) Then ShowUsage
If (ShowWindow < 0) Or (ShowWindow > 65535) Then ShowUsage

Computer = Args.Named("computer")
UserName = Args.Named("username")
Password = Args.Named("password")

Wait = Args.Named.Exists("wait")
Elapsed = Args.Named.Exists("elapsed")

' Создание экземпляра класса
' BEGIN CALLOUT B
Set WMIRun = New WMIExec
' END CALLOUT B

Result = WMIRun.ConnectServer(Computer, UserName, Password)
If Result <> 0 Then
WScript.Echo "Error 0x" & Hex(Result) & " connecting to '" _
& Computer & "'"
WScript.Quit Result
End If

If Elapsed Then StartTime = Now()

Result = WMIRun.RunProgram(Command, StartIn, ShowWindow)
If Result <> 0 Then
WScript.Echo "Error 0x" & Hex(Result) & " starting '" _
& Command & "'"
WScript.Quit Result
End If

' Вывод сообщения о начальном состоянии
WScript.Echo WMIRun.Status

If Wait Then
' Пока процесс запущен, выполнение сценария приостанавливается
Do While WMIRun.ProcessExists()
WScript.Sleep PROCESS_CHECK_INTERVAL
Loop
If Elapsed Then
EndTime = Now()
TimeInfo = "Started: " & CStr(StartTime) & vbNewLine _
& "Ended: " & CStr(EndTime) & vbNewLine _
& "Elapsed: " & GetElapsed(DateDiff("s", StartTime, EndTime))
' Если сценарий запущен через wscript.exe,
' выводится полная информация о статусе
If ScriptHost() = "wscript.exe" Then
Output = WMIRun.Status & vbNewLine & TimeInfo
Else
Output = TimeInfo
End If
WScript.Echo Output
End If
End If
End Sub

 

Таблица 1. Преимущества и недостатки программных инструментов удаленного запуска программ

 

Средство

Преимущества

Недостатки

PsExec

Программа запускается в интерактивном режиме.

Может использовать текущие параметры учетной записи

Альтернативные параметры учетной записи передаются открытым текстом

В некоторых случаях использование утилит третьих фирм может быть запрещено

Планировщик заданий Task Scheduler

Безопасность

Если запускается не от имени системной учетной записи (LocalSystem), то требует задания параметров учетной записи

Сложно запускать программы в интерактивном режиме

WMI

Безопасность

Может использовать текущие параметры учетной записи

Удаленные программы не могут запускаться в интерактивном режиме.

Удаленные программы не имеют доступа к сети


Добавление двойных кавычек в строку команды

Исполнительный механизм Windows Script Host (WSH) считывает аргументы из командной строки запуска сценария и помещает их в объект WshArguments, обеспечивая таким образом доступ как к аргументам, так и к связанным с ними коллекциям. В качестве символа-разделителя аргументов обработчик командной строки использует двойные кавычки ("), поэтому допускается использование аргументов, содержащих пробелы, заключенных в двойные кавычки. В тех случаях, когда сам аргумент содержит кавычки, это приводит к проблемам, поскольку сам обработчик не имеет встроенного механизма добавления символов кавычек ("). Предположим, что мы хотим запустить сценарий RunProgram.vbs с помощью следующей команды запуска:

RunProgram.vbs
"C:Program FilesTest ProgramTest.exe
C:Program FilesTest.txt"

При этом метод Create класса WMI Win32_Process сможет успешно запустить программу (C:Program FilesTest ProgramTest.exe), однако параметр (C:Program FilesTest.txt) также содержит пробелы, а, следовательно, должен быть заключен в кавычки, чтобы программа Test.exe рассматривала его как один параметр. Для обработки данной ситуации в RunProgram.vbs задействована функция Unescape языка VBScript. Она берет строку, содержащую последовательность символов %xx (где xx - шестнадцатеричное представление соответствующего символа ASCII) и заменяет требуемый символ его ASCII-эквивалентом. Двойным кавычкам в ASCII соответствует значение 34 (т.е., 22 в шестнадцатеричном виде), поэтому вместо приведенной выше команды следует вводить строку:

RunProgram.vbs "%22C:Program FilesTest ProgramTest.exe%22
%22C:Program FilesTest.txt%22"

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