Снабдить сценарий графическим интерфейсом можно, воспользовавшись возможностями Windows Script Host (WSH). WSH позволяет устанавливать соединения между источниками событий, генерируемых созданными им объектами COM, и специальными функциями внутри сценария. Сценарий может передать данные на сервер COM и продолжить свое выполнение. Когда в результате некоторого события происходит ошибка в сервере COM, в сценарии автоматически выполняется процедура обработки данного события.

В принципе, для предоставления графического интерфейса сценарию можно использовать практически любой COM-объект, который имеет графический интерфейс и генерирует некоторое событие при выходе. Лично я предпочитаю Microsoft Internet Explorer (IE), поскольку данный компонент имеется практически на всех Windows-компьютерах, а его универсальность может оказаться весьма полезной для расширения функциональности сценариев. Сначала я разъясню изложенную идею на примере простой формы, после чего будет описан некоторый упаковщик, предоставляющий пару несложных функций управления ходом выполнения, которые далее вы сможете использовать в любом сценарии.

Следует отметить, что данный упаковщик не является средством на все случаи жизни. В частности, когда через него осуществляется остановка сценария, то при этом не выполняются никакие процедуры очистки. Он всего лишь формирует окно для просмотра хода выполнения любого запущенного сценария, а также предоставляет возможность для его экстренной остановки. Недостатком предлагаемого решения является также и то, что для запуска IE необходимо достаточное количество памяти, что может стать существенной проблемой при его запуске на компьютерах с ограниченным количеством памяти и на низкоскоростных системах. Однако накладные расходы, связанные с выделением памяти резко сокращаются, если таким же образом запускается вторая копия IE. Если при первом запуске IE из сценария для него требуется 9300 Кб оперативной памяти, то второй запущенный экземпляр IE требует уже 500Кб памяти или чуть больше, а каждая последующая запущенная копия IE требует уже около 200Кб дополнительной памяти. При этом IE не выполняет каких-либо действий, поэтому сам по себе он практически не использует ресурсы процессора.

Перехват событий IE

Компонент IE имеет не зависящий от версии программный идентификатор (ProgID) InternetExplorer.Application, а также может генерировать несколько событий, одно из которых называется onQuit и происходит при завершении работы IE путем нажатия кнопки Close, комбинации клавиш Alt+F4 или при выборе в меню File пункта Exit. Остановка сценария происходит при закрытии запущенного им экземпляра IE, для чего используются имеющиеся в WSH возможности обработки событий.

Базовый код для запуска и остановки IE приведен в Листинге 1. Как видно из фрагмента, отмеченного меткой A, для запуска IE используется метод CreateObject объекта WScript (но не внутренний метод VBScript CreateObject). Метод WScript.CreateObject отличается от обычного CreateObject тем, что он позволяет работать с событиями в сценариях. При просмотре фрагмента кода с меткой A у вас может возникнуть вопрос, для чего нужен аргумент "IE_". В описании метода WScript.CreateObject, имеющемся в документации (http://msdn.microsoft.com/library/en-us/script56/html/wsmthcreateobject.asp), сказано, что необязательный аргумент strPrefix есть "строковое значение, представляющее собой префикс функции". Например, если аргумент strPrefix имеет значение MYOBJ_, и объект генерирует событие с именем OnBegin, тогда Windows Script Host вызовет имеющуюся в сценарии процедуру MYOBJ_OnBegin. Метод CreateObject возвращает указатель на интерфейс IDispatch объекта. Обратите внимание на то, что в оригинальной документации содержится ошибка, которую я здесь исправил. В документации пропущен символ подчеркивания при первом упоминании MYOBJ. Кстати говоря, если вас интересует, почему в качестве последнего символа префикса используется именно знак подчеркивания, а не какой либо другой символ, то ответ заключается в том, что так сложилось исторически. В Visual Basic (VB), как и в других средах программирования, автоматически создающими процедуры обработки (приемники) событий, порождаемых объектами, имя приемника события традиционно содержит имя объекта, присоединенное через символ подчеркивания.

А что нам говорит документация? Только то, что мы можем включать в сценарии свои обработчики событий. Итак, IE порождает событие onQuit, для соответствующих функций объявлен префикс IE_, поэтому можно включить в сценарий процедуру IE_onQuit. В результате, если IE генерирует событие onQuit, то сценарий, независимо от того, что он делает в данный момент, немедленно вызывает процедуру IE_onQuit (см. фрагмент кода с меткой C Листинга 1). Следует отметить, что возможность перехвата событий, генерируемых созданными в сценарии объектами, может иметь много практических применений. В частности, это позволяет иметь механизм принудительного завершения в сценариях, перешедших в бесконечный цикл или выполняющихся чрезмерно долго.

Средняя часть сценария (код, отмеченный на Листинге 1 меткой B) начинается с того, что окно IE делается видимым. Internet Explorer, как и многие другие серверы COM, не делается видимым при запуске автоматически. Далее запускается цикл, который, будучи предоставлен сам себе, является бесконечным. Команда wscript.sleep является стандартным средством, применяемым в тех случаях, когда сценарий должен выполнять циклическую проверку, ожидая некоторого события. Здесь следует отметить, что если не предусмотреть в цикле наличие периодических пауз, то он может поглотить существенную часть ресурсов процессора.

При запуске сценария через CScript или WScript открывается окно IE. Если при этом посмотреть на список диспетчера задач, то там будет отображаться запущенный сервер сценариев (соответственно, CScript или WScript). При закрытии окна IE на экране отображается слово exiting (выход). Если сценарий был запущен через сервер CScript, то после закрытия окна IE имя CScript сразу же исчезнет из списка запущенных задач. Если же для запуска сценария использовался сервер WScript, то тогда, для того чтобы WScript завершил выполнение, нужно закрыть окно WScript.Echo.

Корректное завершение

Хотя описанная технология позволяет достаточно просто реализовать возможность аварийного завершения сценария, она однако имеет ряд недостатков. Во-первых, при запуске нескольких полноразмерных копий IE засоряется рабочий стол и занимается значительная площадь экранного пространства. Во-вторых, все эти окна IE являются полностью функциональными, в результате чего может возникнуть ситуация, когда пользователь непредумышленно воспользуется одним из окон как обычным браузером, после чего закроет его. Результатом этого явится непредсказуемое поведение того сценария, из которого было запущено данное окно IE. И еще одна проблема заключается в том, что если на компьютере одновременно запущено несколько сценариев, то определить какому из них принадлежит то или иное окно IE довольно затруднительно.

Все эти неудобства можно легко устранить, если воспользоваться возможностями объектной модели IE. Когда вы начнете работать с IE, вас удивит то богатство и разнообразие возможностей, которое предоставляет его объектная модель для эффективного расширения возможностей WSH. В сценарии на Листинге 2 показан пример того, как можно организовать запуск IE с помощью несложного встраиваемого кода, который можно далее использовать в любом сценарии. В данном примере устранены все перечисленные выше проблемы, и весьма маловероятно, что его применение может как-то негативно сказаться на работоспособности вашего сценария в целом.

Взаимодействие объектной модели IE с другой процедурой может быть организовано очень просто. Вместо того чтобы создавать прямую ссылку на объект IE, как было показано в примере Листинга 1, во фрагменте кода, обозначенного в Листинге 2 меткой A, сначала объявляется общедоступная (public) переменная IE, а затем вызывается процедура с именем IEScriptWindow. Обработчик события IE_onQuit (Листинг 2, код с меткой B) осуществляет принудительный выход из сценария при закрытии пользователем окна IE. Сама процедура IEScriptWindow, настраивающая параметры окна IE, предназначенного для контроля выполнения сценария, обозначена на Листинге 2 меткой C. Для того чтобы использовать пример с Листинга 2 в существующем сценарии нужно всего лишь вставить код, обозначенный меткой A, перед текстом основного сценария, а код обозначенный метками B и C после текста сценария.

Процедура IEScriptWindow представляет собой пример того, как можно работать с некоторыми свойствами и методами объекта IE. Данный пример может послужить отправной точкой для каких-либо ваших идей по дальнейшей модификации предлагаемого программного инструмента. Более подробно свойства и методы IE описаны в документации Microsoft Developer Network (MSDN) Library, которая доступна по адресу: http://msdn.microsoft.com/workshop/browser/webbrowser/reference/objects/internetexplorer.asp.

Меткой D на Листинге 2 обозначен фрагмент кода, предписывающий IE отобразить пустую локальную страницу, после чего запускается цикл, выполняющийся до момента полной готовности IE к работе. Данный подход является хорошей практикой, поскольку компонент IE представляет собой скорее некоторую совокупность слабосвязанных компонентов, чем просто одно приложение. Любое окно IE содержит в себе документ, который имеет тело (body), содержащее, в свою очередь набор определенных элементов. Сам IE запускается и начинает работать до того, как полностью инициализируется содержащийся в нем документ, даже если по умолчанию используется пустая страница. Отображение пустой страницы до момента полной готовности IE к работе предотвращает возможность преждевременного завершения сценария с выдачей невнятного сообщения об ошибке. Когда IE полностью готов к работе, его свойство ReadyState принимает значение READYSTATE_COMPLETE.

С помощью кода, обозначенного на Листинге 2 меткой E, удаляются ненужные элементы окна, что необходимо для исключения возможности случайного использования данного экземпляра IE в качестве браузера для навигации по Web-сайтам. Каждая из показанных здесь строк кода удаляет из окна строки адреса и меню, а также панель инструментов и строку состояния, так что, как вы можете видеть, данная копия IE будет иметь весьма нестандартный вид.

Следующим шагом является задание размеров окна IE, что иллюстрирует код, обозначенный на Листинге 2 меткой F. Первая строка в данном фрагменте служит для того чтобы отключить возможность нажатия кнопки Maximize и, соответственно, растягивания окна IE на весь экран (при этом возможности сворачивания и восстановления окна сохраняются). В принципе вы можете установить значения ширины и высоты окна равными 0, в результате чего на экране будет виден только значок в панели задач, но я предпочитаю задавать высоту 52 пикселя и ширину 200 пикселей. Если задать высоту в 32 пикселя, тогда на экране отобразится только строка заголовка, в то время как чуть больший размер окна по высоте позволит отображать в теле документа всплывающий текст. Ширина окна, равная 200 пикселей, достаточна для отображения в этом окне имени файла из восьми символов и трех символов расширения даже в том случае если на компьютере по умолчанию включен режим отображения с крупными шрифтами.

Хорошо бы, чтобы в окне IE была также представлена информация о том, какой именно сценарий сейчас запущен. Для этого можно воспользоваться свойством Document.Title, присвоив ему значение, соответствующее описанию полного пути к запускаемому сценарию, однако при этом может потребоваться расширение строки заголовка, к тому же нужно будет предварительно оценить длину строки, чтобы описание пути могло там полностью поместиться. Следует также учитывать и то, что длина строки заголовка окна IE не может быть больше 95 символов. В первой строке фрагмента кода с меткой G Листинга 2 в качестве заголовка устанавливается имя файла сценария: Wscript.ScriptName. Если для своих сценариев вы используете стандартный способ именования файлов (восемь символов для имени и три для расширения), то в этом случае имя файла прекрасно впишется в строку заголовка.

Для вывода дополнительной информации в сценарии используется свойство Body.Title. Для большинства элементов Web-страницы их свойство Title может быть использовано для отображения всплывающего текста, появляющегося, когда пользователь подводит и задерживает на данном элементе указатель мыши. Я задействовал свойство Body.Title для отображения времени запуска сценария и полного пути к нему, при этом IE отображает эту достаточно объемную информацию только тогда, когда указатель мыши задерживается на небольшом элементе тела.

 И, наконец, фрагмент с меткой G, который дополняет сценарий еще двумя особенностями. Во-первых, окно IE автоматически снабжается полосами прокрутки. Однако на окне малого размера, которое используется в нашем сценарии, эти полосы прокрутки смотрятся достаточно уродливо, поэтому я скрыл их, присвоив свойству Body.Scroll значение No. Кроме этого, был добавлен последний штрих, который заключается в том, чтобы сделать это окно видимым.

При запуске сценария, приведенного в Листинге 2, на экран выводится окно, показанное на Рисунке 1. Как видно из этого рисунка, окно IE имеет размер, достаточный для отображения в строке заголовка имени сценария с некоторым запасом, при этом кнопка Maximize неактивна, а кнопки Minimize и Close доступны. Когда пользователь подводит указатель мыши в область тела окна и задерживает его там, то во всплывающем окне отображается время запуска сценария и полный путь к нему.

Очень хорошо также и то, что код запуска IE очень просто интегрируется в любые сценарии. Для того чтобы запустить описанное окно IE из вашего сценария, достаточно включить в него процедуры IE_onQuit и IEScriptWindow. Этим будет обеспечен запуск процедуры IEScriptWindow в начале вашего сценария.

Использование сценария IE Control Window

Окно IE Control Window наилучшим образом подходит для применения при обработке пакетных заданий, выполняющихся продолжительное время, работу которых можно безопасно прервать в любой момент. Одной из таких задач является сценарий, обрабатывающий большое количество документов. В листинге 3 показан пример сценария такого типа. Он просматривает каталог, имя которого определяется переменной targetPath, а затем сжимает все находящиеся в нем несжатые документы, имеющие дату создания, более раннюю, чем та, которая задается переменной maxAge. Смысл данного примера состоит не в технических деталях, а в том, что имеется практическая задача, которая может выполняться значительное время, и может возникнуть необходимость ее остановки.

Для того чтобы снабдить сценарий сжатия файлов графическим интерфейсом нужно просто вставить его текст в середину сценария, формирующего окно IE. Как показано в листинге 4, код сценария сжатия файлов (обозначенный в листинге 4 меткой A) вставляется в сценарий запуска окна IE сразу после вызова процедуры IEScriptWindow. Здесь имеется еще один дополнительный нюанс: в сценарий сжатия файлов сразу за последней строкой его основного текста добавлена команда IE.Quit (фрагмент с меткой B листинга 4), закрывающая окно IE по завершении работы данного сценария, что служит индикатором того, что сценарий сжатия файлов завершил работу.

Безусловно, возможность принудительной остановки требуется далеко не для всех сценариев. Например, если внезапно остановить работу сценария, выполняющего сложные задачи администрирования, то возникшие в результате этого побочные эффекты будут непредсказуемы. Но при реализации задач, подобных рассмотренной нами процедуре сжатия файлов, которые можно без фатальных последствий останавливать и вновь запускать, использование окна IE для контроля хода выполнения сценария может оказаться весьма полезным.


Листинг 1. Сценарий для запуска и завершения работы IE
Option Explicit
Dim IE
 
' BEGIN Callout A
Set IE = WScript.CreateObject( _
        "InternetExplorer.Application", "IE_")
' END Callout A
 
' BEGIN Callout B
IE.Visible = True
Do
        wscript.sleep 10
Loop
' END Callout B
 
' BEGIN Callout C
Sub IE_onQuit
        WScript.Echo "exiting"
        WScript.Quit
End Sub

' END Callout C

Листинг 2. Сценарий IE Control Window
Option Explicit
 
' BEGIN Callout A
Public IE
IEScriptWindow
' END Callout A
 
BEGIN COMMENT
' Начало тела основного сценария.
END COMMENT
Do
        WScript.Sleep 10
Loop
BEGIN COMMENT
' Конец тела основного сценария.
END COMMENT
 
' BEGIN Callout B
Sub IE_onQuit
        WScript.Quit
End Sub
' END Callout B
 
' BEGIN Callout C
Sub IEScriptWindow()
BEGIN COMMENT
        ' В качестве сервера сценариев используются WScript или CScript.
END COMMENT
        CONST READYSTATE_COMPLETE = 4
BEGIN COMMENT
        ' ВНИМАНИЕ: Если нужно обрабатывать события,
        ' то необходимо использовать метод WScript.CreateObject
        ' и задать соответствующий префикс для события.
END COMMENT
        Set IE = WScript.CreateObject( _
                "InternetExplorer.Application", "IE_")
BEGIN COMMENT
        ' В некоторых случаях IE готов к работе не сразу после запуска,
        ' поэтому для его инициализации включаем навигацию,
        ' затем запускаем цикл ожидания до момента,
        ' когда статус IE примет значение READYSTATE_COMPLETE.
END COMMENT
' BEGIN Callout D
        IE.Navigate "about:blank"
        Do until IE.ReadyState = READYSTATE_COMPLETE
                WScript.Sleep 10
        Loop
' END Callout D
 
BEGIN COMMENT
' Удаляем ненужные элементы пользовательского интерфейса.
END COMMENT
' BEGIN Callout E
        IE.AddressBar = False
        IE.MenuBar = False
        IE.ToolBar = False
        IE.StatusBar = False
' END Callout E
 
BEGIN COMMENT
' Задаем размеры окна.
' Для некоторых дисплеев может потребоваться настройка окна по высоте.
END COMMENT
' BEGIN Callout F
        IE.Resizable = False
        IE.Height = 52
        IE.Width = 200
' END Callout F
        
BEGIN COMMENT
' Формируем заголовок и всплывающий текст подсказки в теле окна.
END COMMENT
' BEGIN Callout G
        IE.Document.Title = WScript.ScriptName & String(30, "_")
        IE.Document.Body.Title = "Start Time: " & Now & vbCrLf _
                & "Script: " & WScript.ScriptFullName
        IE.Document.Body.Scroll = "No"
        IE.Visible = True
' END Callout G
End Sub
' END Callout C
Листинг 3. Сценарий сжатия файлов
' Битовая маска сжатия.
Const Compressed = &H800
Public fso, sh, MaxAge
Dim targetPath
MaxAge = 90
targetPath = "D:public"
Set fso = CreateObject("Scripting.FileSystemObject")
Set sh = CreateObject("WScript.Shell")
 
RecurseCompress targetPath
 
Sub RecurseCompress(sFol)
        Dim fld, files, folder, file, cmd
        Dim isOld, isNotCompressed, isDocument
        Set fld = fso.GetFolder(sFol)
        set files = fld.files
        for each file in files
                isOld = CBool( (Now() - file.DateLastAccessed) > MaxAge)
                isNotCompressed = CBool(file.Attributes < Compressed)
                isDocument = CBool(InStr(file.type, "Document"))
                ' Сжатию подвергаются только старые несжатые документы.
                If isOld and isDocument and isNotCompressed Then
                    ' Обратите внимание
                    ' на дополнительные внешние кавычки,
                    ' которые необходимы, чтобы обрабатывать файлы,
                    ' описание путей к которым содержит пробелы.
                    cmd = "%COMSPEC% /c compact /c """ & file.path & """"
                    ' При запуске внешней команды используются
                    ' режим скрытия (0) окна командной строки 
                    ' и режим ожидания (true) 
                    ' завершения выполнения команды,
                    ' таким образом, данные на экран не выводятся,
                    ' что высвобождает ресурсы ЦПУ.
                    Sh.Run cmd, 0, true
                end if
        next
        ' Цикл, необходимый для рекурсивного просмотра подкаталогов.
        If fld.SubFolders.Count > 0 Then
                For Each folder In fld.SubFolders
                    RecurseCompress folder.Path
                Next
        End If
End Sub
Листинг 4. Внедрение сценария сжатия файлов в IE Control Window
Option Explicit
 
Public IE
IEScriptWindow
 
' BEGIN Callout A
' Текст с меткой A представляет собой внешний сценарий,
' внедряемый в сценарий IE control window.
' Битовая маска сжатия.
Const Compressed = &H800
Public fso, sh, MaxAge
Dim targetPath
MaxAge = 90
targetPath = "D:Public"
Set fso = CreateObject("Scripting.FileSystemObject")
Set sh = CreateObject("WScript.Shell")
 
RecurseCompress targetPath
 
' BEGIN Callout B
IE.Quit
' END Callout B
 
Sub RecurseCompress(sFol)
        Dim fld, files, folder, file, cmd
        Dim isOld, isNotCompressed, isDocument
        Set fld = fso.GetFolder(sFol)
        set files = fld.files
        for each file in files
                isOld = CBool( (Now() - file.DateLastAccessed) > MaxAge)
                isNotCompressed = CBool(file.Attributes < Compressed)
                isDocument = CBool(InStr(file.type, "Document"))
                ' Сжатию подвергаются только старые несжатые документы.
                If isOld and isDocument and isNotCompressed Then
                    ' Обратите внимание
                    ' на дополнительные внешние кавычки,
                    ' которые необходимы, чтобы обрабатывать файлы,
                    ' описание путей к которым содержит пробелы.
                    cmd = "%COMSPEC% /c compact /c """ & file.path & """"
                    wscript.echo cmd
                    ' При запуске внешней команды используются
                    ' режим скрытия (0) окна командной строки 
                    ' и режим ожидания (true) 
                    ' завершения выполнения команды,
                    ' таким образом, данные на экран не выводятся,
                    ' что высвобождает ресурсы ЦПУ.
                    Sh.Run cmd, 0, true
                end if
        next
        ' Цикл, необходимый для рекурсивного просмотра подкаталогов.
        If fld.SubFolders.Count > 0 Then
                For Each folder In fld.SubFolders
                    RecurseCompress folder.Path
                Next
        End If
End Sub
' END Callout A
 
Sub IE_onQuit
        WScript.Quit
End Sub
 
Sub IEScriptWindow()
        ' В качестве сервера сценариев используются WScript или CScript.
        CONST READYSTATE_COMPLETE = 4
        ' ВНИМАНИЕ: Если нужно обрабатывать события,
        ' то необходимо использовать метод WScript.CreateObject
        ' и задать соответствующий префикс для события.
        Set IE = WScript.CreateObject( _
                "InternetExplorer.Application", "IE_")
        ' В некоторых случаях IE готов к работе не сразу после запуска,
        ' поэтому для его инициализации включаем навигацию,
        ' затем запускаем цикл ожидания до момента,
        ' когда статус IE примет значение READYSTATE_COMPLETE.
        IE.Navigate "about:blank"
        Do until IE.ReadyState = READYSTATE_COMPLETE
                WScript.Sleep 10
        Loop
 
        ' Удаляем ненужные элементы пользовательского интерфейса.
        IE.AddressBar = False
        IE.MenuBar = False
        IE.ToolBar = False
        IE.StatusBar = False
 
        ' Задаем размеры окна.
        ' Для некоторых дисплеев может потребоваться
        ' настройка окна по высоте.
        IE.Resizable = False
        IE.Height = 52
        IE.Width = 200
 
        ' Формируем заголовок и всплывающий текст подсказки в теле окна.
        IE.Document.Title = WScript.ScriptName & String(30, "_")
        IE.Document.Body.Title = "Start Time: " & Now & vbCrLf _
                & "Script: " & WScript.ScriptFullName
        IE.Document.Body.Scroll = "No"
        IE.Visible = True
End Sub