Пожалуй, многие знают, что Windows начиная с версии 98 имеет по умолчанию в своем составе Windows Script Host (WSH), который позволяет исполнять скрипты на языках VBScript и JScript, но далеко не каждый хотя бы раз пользовался этой возможностью. В этой статье я приведу примеры полезных сниппетов и скриптов для WSH и попробую убедить вас в том, что вещь это действительно стоящая. Я также расскажу об очень занимательных и полезных возможностях WSH, о которых практически никто не знает, и информацию о которых в интернете найти весьма непросто.
Для начала немного о языках, поддерживающихся WSH. JScript - это, по сути, JavaScript с несколько измененной объектной моделью (например, в нем нет объекта window, как в браузерах, зато добавлен объект WScript, позволяющий взаимодействовать со средой, в которой исполняется скрипт). VBScript базируется на синтаксисе и возможностях Visual Basic 6 (и, возможно, более ранних версий). Оба языка имеют приблизительно одинаковые возможности. Кроме того, можно установить и другие языки для WSH, например, PerlScript, который, как вы уже догадались, базируется на Perl'е. Для этого следует воспользоваться, например, инсталлятором ActiveState Perl:
В Windows по умолчанию расширение файла .js ассоциировано с JScript-скриптами, .vbs - с VBScript. При установке PerlScript появляется ассоциация .pls - с PerlScript-скриптами. Скрипты js и vbs можно закодировать с помощью утилиты от Microsoft screnc.exe, получив на выходе файл с расширением .jse или .vbe, соответственно. К сожалению, такое кодирование защитит лишь от неопытных пользователей - множество раскодировщиков можно найти в Google. Кроме того, при закодировании бывают проблемы с русским текстом.
Еще одной замечательной особенностью WSH является то, что он позволяет комбинировать все установленные в системе скриптовые языки в одном файле с расширением .wsf. Например, VBScript предоставляет функцию, отображающую окошко для ввода текста (InputBox), и она вам очень нужна, но скрипт свой вы пишете на JScript, который такой функцией не располагает. Решается проблема очень просто - создать файл wsf со следующим содержанием:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="windows-1251"?> <job id="MyTestJob"> <script language="VBScript"> <![CDATA[ Function WSHInputBox(Message, Title, Value) WSHInputBox = InputBox(Message, Title, Value) End Function ]]> </script> <script language="JScript"> <![CDATA[ //Ваш код на JS var name = WSHInputBox("Введите ваше имя:", "Запрос", "Вася Пупкин"); WScript.Echo("Имя: " + name); ]]> </script> </job> |
Таким же образом можно скомбинировать, например, JScript и PerlScript, если PerlScript у вас установлен:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="windows-1251"?> <job id="MyTestJob"> <script language="PerlScript"> <![CDATA[ use strict; our $WScript; use LWP::UserAgent; sub do_request { my $lwp = new LWP::UserAgent; my $response = $lwp->get($_[0]); if($response->is_success) { return $response->headers->as_string; } else { return $response->error_as_HTML; } } ]]> </script> <script language="JScript"> <![CDATA[ WScript.Echo(do_request("http://ya.ru")); ]]> </script> </job> |
Небольшое отступление - расскажу о запуске скриптов для WSH. По двойному клику мышкой они по умолчанию запускаются с помощью программы wscript.exe. В этом случае все вызовы WScript.Echo транслируются в обычные messagebox'ы. Если вы используете скрипт для автоматизации какого-либо процесса, например, сборки какого-либо проекта, и желаете выводить множество сообщений, то следует скрипт запускать с помощью программы cscript.exe, которая обращения к WScript.Echo транслирует в выводы в консоль. Запустить скрипт в консольном варианте можно, создав bat-файл с примерно таким содержимым:
1 2 3 |
@echo off cscript my_script.wsf [параметры] pause |
Можно также кликнуть на файле скрипта и вызвать меню "Open with command prompt".
Возможно, я кого-то удивлю, если скажу, что из скриптов для WSH можно с легкостью использовать классы .NET! Приведу пример на JScript (js-файл):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
function vote_form() { //Создаем объект формы и всякие контролы this.form = WScript.CreateObject("System.Windows.Forms.Form"); this.radioButton1 = WScript.CreateObject("System.Windows.Forms.RadioButton"); this.radioButton2 = WScript.CreateObject("System.Windows.Forms.RadioButton"); this.radioButton3 = WScript.CreateObject("System.Windows.Forms.RadioButton"); this.radioButton4 = WScript.CreateObject("System.Windows.Forms.RadioButton"); this.button1 = WScript.CreateObject("System.Windows.Forms.Button"); this.button2 = WScript.CreateObject("System.Windows.Forms.Button"); this.linkLabel1 = WScript.CreateObject("System.Windows.Forms.LinkLabel"); //Настраиваем контролы with(this.radioButton1) { Parent = this.form; Checked = true; Left = 12; Top = 12; Width = 110; Height = 17; TabStop = true; Text = "VBScript"; } with(this.radioButton2) { Parent = this.form; Left = 12; Top = 35; Width = 110; Height = 17; TabStop = true; Text = "JScript"; } with(this.radioButton3) { Parent = this.form; Left = 12; Top = 58; Width = 110; Height = 17; TabStop = true; Text = "PerlScript"; } with(this.radioButton4) { Parent = this.form; Left = 12; Top = 81; Width = 110; Height = 17; TabStop = true; Text = "Единая Россия"; } with(this.button1) { Parent = this.form; Left = 12; Top = 112; Width = 85; Height = 23; Text = "Да!"; DialogResult = 1; } with(this.button2) { Parent = this.form; Left = 125; Top = 112; Width = 85; Height = 23; Text = "Идите вы!"; DialogResult = 0; } with(this.linkLabel1) { Parent = this.form; Left = 167; Top = 9; Width = 45; Height = 15; Text = "kaimi.ru"; } //настраиваем форму with(this.form) { Width = 222; Height = 125; Text = "Какой язык вам больше по душе?"; AutoSize = true; FormBorderStyle = 5; //FixedToolWindow CancelButton = this.button1; CancelButton = this.button2; } //Отобразить форму и вернуть true, если пользователь нажал на первую кнопку this.show = function() { this.form.ShowDialog(); return this.form.DialogResult == 1; }; //Получить выбранный результат (см. выше, на форме 4 radio button'а) this.result = function() { if(this.radioButton1.Checked) return this.radioButton1.Text; else if(this.radioButton2.Checked) return this.radioButton2.Text; else if(this.radioButton3.Checked) return this.radioButton3.Text; else if(this.radioButton4.Checked) return this.radioButton4.Text; }; }; //Создаем форму var my_form = new vote_form; //Предлагаем пользователю сделать выбор while(true) { if(my_form.show()) { WScript.Echo("Вы выбрали: " + my_form.result()); break; } else { WScript.Echo("Ну как же так, надо же выбрать!"); } } |
Выполнив этот скрипт, увидим такую форму:
И все это создано с помощью .NET-классов! Есть, правда, в этом некоторые сложности. Во-первых, я не нашел путей для взаимодействия с делегатами, которые используются при обработке событий в .NET. Во-вторых, не существует способа вызвать через CreateObject конструктор COM-объекта, принимающий параметры (это относится не только к .NET, кстати). В-третьих, по умолчанию в скриптах доступны лишь некоторые .NET-сборки и классы:
System.Collections.Queue
System.Collections.Stack
System.Collections.ArrayList
System.Collections.SortedList
System.Collections.Hashtable
System.IO.StringWriter
System.IO.MemoryStream
System.Text.StringBuilder
System.Random
С другой стороны, опубликовать .NET-сборку, чтобы она стала доступной через COM-интерфейсы (которые и используются в WSH), совсем несложно. Если вышеприведенный скрипт с Windows Forms у вас не заработал (что, скорее всего, так и есть), наберите в консоли команду:
1 |
%WINDIR%\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe System.Windows.Forms.dll /codebase |
либо, если вы пользуетесь 64-битной операционной системой, то
1 |
%WINDIR%\Microsoft.NET\Framework64\v2.0.50727\RegAsm.exe System.Windows.Forms.dll /codebase |
Эта команда опубликует сборку System.Windows.Forms и она станет доступной через COM-интерфейсы, после чего вы сможете использовать классы из нее в скриптах. К сожалению, не все сборки можно зарегистрировать, а только те, которые имеют аттрибут ComVisible=true (если такой аттрибут у сборки есть, об этом говорится на соответствующих страницах с описанием сборки в MSDN).
Для отмены регистрации сборки выполните приведенную выше команду с ключом /unregister.
Что еще следует знать при использовании .NET-сборок? Часто классы в .NET имеют перегруженные функции с одинаковыми именами и разными типами и количеством параметров. Как вызывать их? Ведь переменные в скриптах практически не типизируются! Все очень просто. Например, возьмем класс System.Random. Он уже зарегистрирован по умолчанию и доступен из WSH. Но он имеет три метода с именем Next. Как вызвать нужный? .NET маппит имена одинаковых методов следующим образом: первый с конца в таблице методов имеет имя Next (в данном случае), следующий - Next_2, далее - Next_3. Где увидеть эту таблицу с правильным порядком функций? Например, в ildasm'е:
Здесь я открыл сборку mscorlib.dll из %WINDIR%\Microsoft.NET\Framework64\v2.0.50727, после чего зашел в неймспейс System и нашел класс Random в нем. Теперь ясно - если мы хотим воспользоваться методом Next, предоставляющим возможность указать минимальное и максимальное значение при генерации рандома, то следует вызвать Next_2 (так как этот метод второй с конца в списке методов Next, смотрите скриншот выше):
1 2 |
var random = WScript.CreateObject("System.Random"); WScript.Echo(random.Next_2(10, 20)); //выводим рандомное число в диапазоне от 10 до 20 |
Еще одной из интересных возможностей скриптов для WSH является поддержка drag-drop'а. На файлы .js, .vbs, .jse, .vbe, .wsf можно перетаскивать другие файлы, и их имена будут доступны через WScript.Arguments.
Итак, подведем итоги. Чем же примечательно написание скриптов на JScript, VBscript или PerlScript? Почему это лучше и проще bat-файлов или PowerShell'а?
[+] Вы сами выбираете знакомый любимый синтаксис. Предпочитаете JavaScript - пишите на нем, обожаете Visual Basic - тогда VBScript для вас!
[+] Вы можете комбинировать эти языки в одном скрипте, тем самым дополняя возможности одного языка фичами другого.
[+] Вам доступны все стандартные особенности выбранного языка. Поддержка выполнения внешних программ, работа с текстовыми и двоичными файлами, регулярные выражения и многое другое прилагается. Поддержка работы с файлами по маске, с сетевыми путями (samba, например) - тоже.
[+] Вы можете использовать множество зарегистрированных COM-классов.
[+] Вы можете использовать многие COM-Visible .NET-сборки.
[+] Вы можете работать с WMI, так как он доступен через COM.
[+] Имеется полная поддержка Unicode, достаточно сохранить файл как Unicode Little Endian.
Масса очевиднейших плюсов. На WSH можно писать мощнейшие приложения и скрипты, которые облегчат вам жизнь и сделают какие-то полезные задачи автоматически.
Сейчас мне остается лишь привести несколько полезных сниппетов, которые пригодятся вам, если вы решите использовать WSH для написания скриптов, производящих автоматическую сборку проектов/парсинг/работу с файлами и т.д. Для примера я буду использовать свой любимый JScript, потому что он имеет наиболее привычный синтаксис и будет понятен большинству из вас. Кроме того, в JScript удобно перехватывать исключения, которые могут быть выброшены функциями COM-классов и объекта WScript, с помощью try-catch. Можно и самим бросать исключения (это штатная возможность языка JavaScript, и, разумеется, она есть в JScript).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var fso = WScript.CreateObject("Scripting.FileSystemObject"); //Создаем объект, позволяющий работать с файловой системой fso.CopyFile("xx.txt", "yy.txt"); //копируем xx.txt в yy.txt fso.CopyFile("C:\\Test\\*.exe", "C:\\test2\\"); //копируем все exe-файлы из C:\Test в C:\test2 fso.DeleteFile("xx.txt"); //Удаляем xx.txt fso.DeleteFile("*.*"); //Удаляем все файлы из текущей папки if(!fso.FolderExists("C:\\Temp123")) fso.CreateFolder("C:\\Temp123"); //Создаем папку, если она не существует //Перечисляем имена всех подпапок на диске C: var folder = fso.GetFolder("C:\\"); if(folder) { var it = new Enumerator(folder.SubFolders); for(; !it.atEnd(); it.moveNext()) WScript.Echo(it.item().path); } |
1 2 3 4 5 6 7 8 9 |
var shell = WScript.CreateObject("WScript.Shell"); WScript.Echo(shell.ExpandEnvironmentStrings("Windows folder: %WINDIR%")); //Получаем значение переменной окружения //Проверяем, установлена ли переменная окружения var check = "%VS100COMNTOOLS%"; if(shell.ExpandEnvironmentStrings(check) != check) WScript.Echo("У вас установлена Visual Studio 2010!"); else WScript.Echo("У вас нет Visual Studio 2010 :("); |
Выполнение программы с ожиданием ее завершения и получением кода возврата:
1 2 3 4 5 6 7 8 9 10 |
try { var shell = WScript.CreateObject("WScript.Shell"); var ret = shell.Run("calc.exe", 1, true); //Выполняем calc.exe WScript.Echo("Код возврата: " + ret); } catch(error) { WScript.Echo("Код ошибки: " + error.number + "\n" + error.description); } |
Выполнение внешней программы с перехватом ее вывода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
try { var shell = WScript.CreateObject("WScript.Shell"); var exec = shell.Exec("find.exe /?"); //Выполняем find.exe с параметром while(exec.Status == 0) //Ждем окончания выполнения команды WScript.Sleep(100); if(exec.ExitCode != 0) //Если произошла ошибка (обычно все консольные программы в случае ошибки возвращают ненулевое значение, но не всегда) throw new Error(exec.ExitCode, "Cannot execute command!"); var output = ""; if(!exec.StdOut.AtEndOfStream) output = exec.StdOut.ReadAll(); //Перехватываем вывод выполненной программы WScript.Echo("FIND.EXE help:\n\n" + output); } catch(error) { WScript.Echo("Код ошибки: " + error.number + "\n" + error.description); } |
Можно также вводить какие-либо данные в программу через Exec.StdIn.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var shell = WScript.CreateObject("WScript.Shell"); //Создаем в ветке HKCU\Software каталог Test123\WScriptTest. //Обратите внимание на то, что в конце пути я указал обратный слеш //Последний параметр - тип создаваемого значения - указывать необязательно. shell.RegWrite("HKCU\\Software\\Test123\\WScriptTest\\", 1, "REG_BINARY"); //Создаем в этом каталоге строковое значение shell.RegWrite("HKCU\\Software\\Test123\\WScriptTest\\myvalue", "xyz", "REG_SZ"); //Меняем значение тем же вызовом shell.RegWrite("HKCU\\Software\\Test123\\WScriptTest\\myvalue", "12345", "REG_SZ"); //Читаем записанное значение WScript.Echo("Записанное значение: " + shell.RegRead("HKCU\\Software\\Test123\\WScriptTest\\myvalue")); //Так как нельзя удалить каталог в реестре с вложенными каталогами, //удаляем сначала вложенный shell.RegDelete("HKCU\\Software\\Test123\\WScriptTest\\"); //А затем внешний shell.RegDelete("HKCU\\Software\\Test123\\"); |
Перечисляем все вложенные ключи реестра в заданной ветке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//Необходимые константы для доступа к реестру var HKEY_CLASSES_ROOT = 0x80000000; var HKEY_CURRENT_USER = 0x80000001; var HKEY_LOCAL_MACHINE = 0x80000002; var HKEY_USERS = 0x80000003; var HKEY_CURRENT_CONFIG = 0x80000005; var locator = WScript.CreateObject("WbemScripting.SWbemLocator"); //Подключимся к WMI локального компьютера var server_conn = locator.ConnectServer(null, "root\\default"); //Получим доступ к реестру var registry = server_conn.Get("StdRegProv"); //Получим метод перечисления ключей var method = registry.Methods_.Item("EnumKey"); //Зададим параметры для вызова метода var input_params = method.InParameters.SpawnInstance_(); input_params.hDefKey = HKEY_CURRENT_USER; //Будем перечислять все ключи в этой ветке реестра input_params.sSubKeyName = "Software\\Microsoft\\Windows\\CurrentVersion\\"; //Выполняем метод перечисления ключей var output = registry.ExecMethod_(method.Name, input_params); var subkeys = output.sNames.toArray(); //Выводим полученные значения for(var key in subkeys) WScript.Echo(subkeys[key]); |
Такую вещь на VBScript можно сделать гораздо проще, как ни странно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const HKEY_CLASSES_ROOT = &H80000000 const HKEY_CURRENT_USER = &H80000001 const HKEY_LOCAL_MACHINE = &H80000002 const HKEY_USERS = &H80000003 const HKEY_CURRENT_CONFIG = &H80000005 'Получили доступ к локальному реестру через WMI Set registry = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv") 'Перечислили имена ключей registry.EnumKey HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion", key_names 'Вывели их For i = 0 To UBound(key_names) WScript.Echo key_names(i) Next |
Выполнение запроса WMI (в этом примере - вывод всех аккаунтов на компьютере):
1 2 3 4 5 6 7 8 |
//Подключились к WMI локального компьютера (".") var root = GetObject("winmgmts:\\\\.\\root\\cimv2"); //Выполнили запрос на получение всех локальных аккаунтов на компьютере var items = root.ExecQuery("SELECT * FROM Win32_Account where LocalAccount = true and SIDType = 1"); //Вывели их имена и описания for(var it = new Enumerator(items); !it.atEnd(); it.moveNext()) WScript.Echo(it.item().Name + " - " + it.item().Description); |
На этом я закончу свое повествование. Думаю, если вы осилили эту статью до конца, вы осознали всю мощь и удобство скриптов для WSH и непременно воспользуетесь этой замечательной функциональностью Windows!
Для дальнейшего изучения могу посоветовать MSDN (там есть множество документации по WSH, и, конечно же, .NET и WMI), и Google (примеров скриптов для WSH там несчетное количество). Удачи в изучении!