Финансы Сайт на котором знают все про финансы

Windows — автоматизация задач с помощью скриптов

Windows — автоматизация задач с помощью скриптов. Интересные возможности WSH

Пожалуй, многие знают, что 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 со следующим содержанием:

%MINIFYHTMLa454ee342e6fe67898c47358b0900b736%%MINIFYHTMLa454ee342e6fe67898c47358b0900b737%

Таким же образом можно скомбинировать, например, JScript и PerlScript, если PerlScript у вас установлен:

%MINIFYHTMLa454ee342e6fe67898c47358b0900b738%%MINIFYHTMLa454ee342e6fe67898c47358b0900b739%

Небольшое отступление — расскажу о запуске скриптов для WSH. По двойному клику мышкой они по умолчанию запускаются с помощью программы wscript.exe. В этом случае все вызовы WScript.Echo транслируются в обычные messagebox’ы. Если вы используете скрипт для автоматизации какого-либо процесса, например, сборки какого-либо проекта, и желаете выводить множество сообщений, то следует скрипт запускать с помощью программы cscript.exe, которая обращения к WScript.Echo транслирует в выводы в консоль. Запустить скрипт в консольном варианте можно, создав bat-файл с примерно таким содержимым:

@echo off
cscript my_script.wsf [параметры]
pause

Можно также кликнуть на файле скрипта и вызвать меню «Open with command prompt».

Возможно, я кого-то удивлю, если скажу, что из скриптов для WSH можно с легкостью использовать классы .NET! Приведу пример на JScript (js-файл):

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 у вас не заработал (что, скорее всего, так и есть), наберите в консоли команду:

%WINDIR%\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe System.Windows.Forms.dll /codebase

либо, если вы пользуетесь 64-битной операционной системой, то

%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, смотрите скриншот выше):

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. Работаем с файловой системой.

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);
}

2. Работаем с переменными окружения.

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 :(");

3. Выполнение внешних программ.

Выполнение программы с ожиданием ее завершения и получением кода возврата:

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);
}

Выполнение внешней программы с перехватом ее вывода:

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.

4. Работаем с реестром Windows.

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\\");

5. Работаем с WMI.

Перечисляем все вложенные ключи реестра в заданной ветке:

//Необходимые константы для доступа к реестру
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 можно сделать гораздо проще, как ни странно:

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 (в этом примере — вывод всех аккаунтов на компьютере):

//Подключились к 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 там несчетное количество). Удачи в изучении!