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 со следующим содержанием: Code: <?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, если он у вас установлен: Code: <?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-файл с примерно таким содержимым: Code: @echo off cscript my_script.wsf [параметры] pause Можно также кликнуть на файле скрипта и вызвать меню "Open with command prompt". Возможно, я кого-то удивлю, если скажу, что из скриптов для WSH можно с легкостью использовать классы .NET! Приведу пример на JScript (js-файл): Code: 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 у вас не заработал (что, скорее всего, так и есть), наберите в консоли команду: Code: %WINDIR%\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe System.Windows.Forms.dll /codebase либо, если вы пользуетесь 64-битной операционной системой, то Code: %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, смотрите скриншот выше): Code: 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. Работаем с файловой системой. Code: 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. Работаем с переменными окружения. Code: 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. Выполнение внешних программ. Выполнение программы с ожиданием ее завершения и получением кода возврата: Code: 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); } Выполнение внешней программы с перехватом ее вывода: Code: 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. Code: 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. Перечисляем все вложенные ключи реестра в заданной ветке: Code: //Необходимые константы для доступа к реестру 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 можно сделать гораздо проще, как ни странно: Code: 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 (в этом примере - вывод всех аккаунтов на компьютере): Code: //Подключились к 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 там несчетное количество). Удачи в изучении! http://kaimi.ru/ http://kaimi.ru/2012/04/windows-script-host/ Воскресенье, 1. Апрель 2012
d3l3t3, ты чокнулся. теперь окончательно. почему на тебя такое находит - постить длинные унылые портянки в пятницу? Windows Script Shell общеизвестно с 2001, с появлением WinXP. неудачная попытка перенести bash script на windows basic. и если ты, уважаемый d3l3t3 опробовал сам, заметил бы сырость и недоделанность. простейшие файловые операции, эта WSH - can not from design. Code: Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. c:\WINNT\system32>dir *.vbs Directory of c:\WINNT\system32 04/14/2008 02:00 PM 97,965 eventquery.vbs 04/14/2008 02:00 PM 167,219 pagefileconfig.vbs 04/14/2008 02:00 PM 35,755 prncnfg.vbs 04/14/2008 02:00 PM 25,415 prndrvr.vbs 04/14/2008 02:00 PM 21,527 prnjobs.vbs 04/14/2008 02:00 PM 32,546 prnmngr.vbs 04/14/2008 02:00 PM 29,454 prnport.vbs 04/14/2008 02:00 PM 15,860 prnqctl.vbs 04/14/2008 02:00 PM 3,708 pubprn.vbs
Судя по всему, ты-то как раз и не пользовался скриптами (и статью, кроме заголовка, не читал). Делал большие системы, удаляющие-собирающие-копирующие-заливающие-правящие файлы-проекты автоматически, исполняющие внешние программы, никаких проблем не было никогда. Все работает быстро и без проблем. Статья, кстати, моя, я в ней описал всякие фичи WSH, о которых не каждый знает, даже несмотря на то, что он давно появился.
пока ты писал статью, Блиц кодил на Windows Script Host. из архива: Code: const formFile="c:\usr\projects\knapster\form.asc" const fileEmpty=0 const Normal=0 const Archive=32 const READ=1 const WRITE=2 const timeOut=120 var icon=48 var button=0 local=new Host() formFileInfo=local.WshFSO.GetFile(formFile) function Host() { this.WshShell=Wscript.CreateObject('Wscript.Shell') this.WshNetwork=Wscript.CreateObject('Wscript.Network') this.WshFSO=new ActiveXObject('Scripting.FileSystemObject') this.WshArgs=Wscript.Arguments this.ProgID=new String(" . : k n a p s t e r . : : . <" + this.WshNetwork.UserName + "@" + this.WshNetwork.ComputerName + "> : .") this.pad=this.WshFSO.GetFolder('.') this.root=this.pad.isRoot() .. this.CMSFileName=CMSFileName this.parseStream=parseStream } function readFile(file) { this.file=WshFSO.OpenTextFile(file,READ,false) this.stream=this.file.ReadAll()+'\n' this.file.Close() } function writeFile(file) { this.file=WshFSO.OpenTextFile(file,WRITE,true) this.file.Write(this.stream) this.file.Close() }
Фигово кодил значит. Обилие файликов "test" со всяческими расширениями подтверждает. У меня все тысячи строк отлично работают (на JScript писал, потому что синтаксис VBS убог).
GRRRL Power, и не упоминай PowerShell )) не стану его устанавливать. эта вредяка хотеть ещё и .NET в придачу.
в этом полностью согласен с тобой. JS - подарок Mozilla Foundation, кодить на нём - одно удовольствие )) Фигово ли кодил, задаeшь вопрос? Вылизал код безупречно, и встроил в CAD E-plan.
Так а в чём всё таки подвох то? Ну я могу ошибаться.. конечно.. 1 числа также видела этот пост в блоге.. и последующие дни.. читала.. У меня там 10 чувство, что где-то тут подвох, возможно, из-за предрассудков с датой и статьи в разделе Статей.. Ошибаюсь?
Ошибаешься, на 1 апреля статью мы запостили без подвоха. У нас в блоге был другой подвох, который второго апреля убрали уже, не связанный со статьей