Константин Леонтьев
Узнай секреты WMI: события и провайдеры
Часть I: дополнительные возможности
Алиса терпеливо ждала, пока Гусеница не соблаговолит снова обратить на нее внимание.
Минуты через две та вынула кальян изо рта, зевнула –раз, другой – и потянулась.
Потом она сползла с гриба и скрылась в траве, бросив Алисе на прощанье:
– Откусишь с одной стороны – подрастешь, с другой – уменьшишься!
– С одной стороны чего? – подумала Алиса. – С другой стороны чего?
– Гриба, – ответила Гусеница, словно услышав вопрос, и исчезла из виду.
С минуту Алиса задумчиво смотрела на гриб, пытаясь определить,
где у него одна сторона, а где – другая; гриб был круглый, и это совсем сбило ее с толку.
Наконец, она решилась: обхватила гриб руками и отломила с каждой стороны по кусочку.
Приключения Алисы в стране чудес.
Чарльз Латвидж Доджсон
(Пер. Н. М. Демуровой)
Вы уже используете WMI, и эта технология помогает вам решать повседневные задачи? Настало время копнуть поглубже и открыть новые возможности WMI, о которых многие, возможно, даже и не догадываются!
В поход за белым кроликом…
После того как азы WMI изучены [1], наша следующая цель – рассмотреть расширенные возможности технологии, до использования которых добирается лишь небольшая часть системных администраторов, так как их применение часто требует написания сценариев, что администраторы ОС Windows делать обычно не любят. Мне хочется сразу отметить, что эти возможности настолько удобны в использовании и полезны для решения нетривиальных задач, что затраты сил и времени на их освоение и применение окупятся с лихвой. К тому же я постараюсь облегчить вам эту задачу, включив в текст большое количество рабочих примеров из реальной жизни, которые послужат хорошими шаблонами для решения ваших собственных задач.
Эта статья рассчитана на подготовленного читателя. К таким читателям я отношу тех, кто уже использует в своей повседневной практике WMI и самостоятельно создает сценарии, использующие WMI для решения задач системного администрирования.
Однако не стоит отчаиваться и тем, кто хочет изучить эту технологию с нуля. Для таких читателей я порекомендовал бы предварительно ознакомиться с моей статьей под названием «Вы все еще не используете WMI?» из предыдущих номеров журнала «Системный администратор» [1].
Для понимания материала этой статьи также необходимы знания по программированию сценариев в среде WSH (Windows Scripting Host). С подробной информацией по этому вопросу можно ознакомиться в переводной книге Гюнтера Борна [2] и в оригинальной документации Microsoft [3]. Все примеры, которые я использую, приведены на языке VBScript и протестированы на ОС Windows XP Service Pack 2 и Windows Server 2003, хотя многие из них так же успешно будут работать и на Windows 2000, правда, для этого потребуется выполнить ряд подготовительных действий.
Напомню, что в предыдущей моей статье о технологии WMI [1] очень кратко рассматривалась возможность обработки событий WMI и объяснялись общие различия между синхронной и асинхронной техникой обработки событий, а также была обозначена тема стандартных подписчиков на события WMI (Standard Event Consumers), которые могут также применяться для обработки событий. В этой статье настал черед рассмотреть все эти темы подробнее и детально разобрать примеры использования основных провайдеров WMI, установленных по умолчанию в ОС Windows.
Типы событий WMI и способы их обработки
Итак, приступим к изучению расширенных возможностей WMI. Для того чтобы мой рассказ об этой теме был более предметным, я решил выбрать конкретную практическую задачку и рассмотреть ее решение поэтапно. Я остановился на задаче отслеживания событий подключения/отключения съемных дисковых устройств USB и реакции на них. Во многих организациях съемные устройства (в основном USB FlashDrive) являются головной болью для службы безопасности и администраторов. Обычно проблемы, связанные с несанкционированным применением пользователями съемных устройств памяти, довольно эффективно решают с помощью различного дополнительного ПО (например, DeviceLock). Мы же с вами попробуем решить часть из них стандартными средствам WMI. Наша цель – зафиксировать факт события подключения таких устройств и выполнить некоторое ответное действие (например, сценарий VBScript).
Для начала давайте рассмотрим простой пример кода на VBScript, использующего WMI для синхронного отслеживания событий подключения съемных дисковых устройств. Он послужит нам отправной точкой в данной работе. Этот пример приведен на листинге 1. Попробуйте запустить этот сценарий командой cscript <имя сценария> и посмотреть на результат его работы при подключении устройств USB FlashDrive.
Листинг 1. Пример сценария синхронного отслеживания подключения устройств USB
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
Set objEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE " & _
"TargetInstance ISA 'Win32_LogicalDisk'" & _
" AND TargetInstance.DriveType = 2")
Wscript.Echo "Ожидаем события ..."
Do While(True)
Set objReceivedEvent = objEvents.NextEvent
Wscript.Echo "Name: " & objReceivedEvent.TargetInstance.Name
Wscript.Echo "Caption: " & objReceivedEvent.TargetInstance.Caption
Wscript.Echo "FileSystem: " & objReceivedEvent.TargetInstance.FileSystem
Wscript.Echo "Description: " & objReceivedEvent.TargetInstance.Description
Loop
Теперь давайте чуть подробнее разберем этот пример. Первые две строки служат для подключения к репозиторию WMI и не вызывают особого интереса. Третий же оператор сценария представляет для нас существенный интерес. Здесь мы вызываем метод ExecNotificationQuery объекта objWMIService (он принадлежит к классу SWbemServices), который регистрирует временный подписчик на события WMI. Этот метод возвращает нам специальный объект-подписчик, который по заданным нами критериям следит за событиями WMI. Этот объект мы сохраняем в переменной objEvents. У класса SWbemServices также существует второй метод регистрации временного подписчика на события WMI, который называется ExecNotificationQueryAsync. Этот метод позволяет регистрировать временные подписчики для асинхронной обработки событий WMI, о нем мы поговорим чуть-чуть позже.
Рассмотрим внимательнее WQL-запрос, с помощью которого наш сценарий подписывается на события WMI. Для того чтобы получать уведомления об интересующих нас событиях, мы делаем выборку объектов специального класса __InstanceCreationEvent, который служит для отслеживания внутренних событий WMI, возникающих при создании экземпляров объектов любого класса, зарегистрированного в репозитории WMI. Заметим, что существуют и другие специальные классы отслеживания внутренних событий WMI, например: __InstanceDeletionEvent, __InstanceModificationEvent, __InstanceOperationEvent или __ClassCreationEvent, __ClassDeletionEvent и другие. Довольно очевидно из их названия, для отслеживания каких именно внутренних событий WMI эти специальные классы созданы. В таблице 1 перечислены все основные системные классы, обеспечивающие отслеживание внутренних событий WMI. Все они выведены из абстрактного WMI класса __Event.
Таблица 1. Список основных классов для подписки на внутренние (intrinsic) события WMI
Внутренне классы событий
|
Описание
|
__ClassCreationEvent
|
Извещает подписчика о создании нового класса WMI
|
__ClassDeletionEvent
|
Извещает подписчика об удалении класса WMI
|
__ClassModificationEvent
|
Извещает подписчика об изменении класса WMI
|
__InstanceCreationEvent
|
Извещает подписчика о создании экземпляра объекта класса WMI
|
__InstanceOperationEvent
|
Извещает подписчика об изменении, удалении или создании экземпляра объекта класса.
|
__InstanceDeletionEvent
|
Извещает подписчика об удалении экземпляра объекта класса WMI
|
__InstanceModificationEvent
|
Извещает подписчика о модификации экземпляра объекта класса WMI
|
__NamespaceCreationEvent
|
Извещает подписчика о создании нового пространства имен WMI
|
__NamespaceDeletionEvent
|
Извещает подписчика об удалении пространства имен WMI
|
__NamespaceModificationEvent
|
Извещает подписчика об изменении в пространстве имен WMI
|
__ConsumerFailureEvent
|
Извещает подписчика о том, что некоторые события не были доставлены подписчику из-за ошибок на стороне подписчика
|
__EventDroppedEvent
|
Извещает подписчика о том, что некоторые события не были доставлены до их подписчика
|
__EventQueueOverflowEvent
|
Извещает подписчика о том, что некоторые события WMI не были доставлены до подписчика из-за переполнения очереди событий
|
__MethodInvocationEvent
|
Извещает подписчика о том, что был выполнен вызов метода WMI.
Не поддерживается на Windows NT/2000
|
Далее обратим внимание на ключевое слово WITHIN в запросе WQL – в отличие от обычного языка SQL ключевое слово WITHIN в языке WQL определяет интервал опроса приходящих событий в секундах или служит для группировки событий (в сочетании с ключевым словом GROUP) в течение указанного количества секунд.
В нашем конкретном случае временный подписчик событий WMI – это объект, который мы получаем в переменную objEvents в результате вызова метода ExecNotificationQuery. Он будет каждые 5 секунд осуществлять проверку всех событий WMI по созданию экземпляров объектов, соответствующих критериям, заданным после ключевого слова WHERE, а именно отслеживать появление новых объектов класса Win32_LogicalDisk, у которых свойство DriveType равно 2.
Закончим разбор сценария анализом цикла ожидания событий. Вполне очевидно, что сам по себе цикл бесконечный и условия завершения не имеет. Внутри тела цикла вызывается метод NextEvent нашего экземпляра объекта objEvents – временного подписчика на события WMI. Этот метод возвращает управление VBS-сценарию в тот момент, когда в системе происходит событие WMI, соответствующее условиям, заданным при создании объекта-подписчика. Возвращаемый этим методом объект является объектом-описателем происшедшего события и имеет свойства TargetInstance и TIME_CREATED. Свойство TargetInstance содержит не что иное как ссылку на экземпляр объекта, вызвавшего это событие.
Теперь необходимо обратить наше пристальное внимание на тот факт, что существуют два типа событий WMI. Внутренние (системные) события WMI и внешние (чуждые). Эти типы событий принципиально отличаются по своему происхождению, обслуживаются разными группами классов WMI и как следствие отличаются по некоторым приемам работы с ними. Все внутренние (intrinsic) классы отслеживания событий WMI выведены непосредственно из абстрактного класса __Event, как я уже упоминал ранее, а вот все внешние (extrinsic) классы событий выведены из класса __ExtrinsicEvent, который уже в свою очередь выведен из класса __Event.
По сути дела за генерацию событий, доступ к которым обеспечивают классы внутренних событий, отвечает само ядро службы WMI, а за генерацию событий, доступ к которым обеспечивают классы внешних событий, отвечают уже WMI-провайдеры. Таким образом, если мы хотим отслеживать события WMI, используя внутренние механизмы службы WMI, то мы будем пользоваться универсальными внутренними (intrinsic) классами. Если же интересующий нас WMI-провайдер предоставляет свои собственные (внешние) классы отслеживания событий, то использовать следует именно их, так как они по определению будут работать эффективнее.
К сожалению, далеко не все провайдеры предоставляют достаточный набор собственных внешних классов для отслеживания событий. В листинге 3 дан пример использования специального внешнего класса отслеживания событий Win32_ProcessStartTrace провайдера Win32. Также в этом листинге используется пример отслеживания событий таймера с использованием внутренних классов отслеживания событий. Хочу обратить ваше внимание на тот факт, что у всех внутренних классов отслеживания событий существует свойство TargetInstance, но его нет у внешних классов отслеживания событий WMI. Зато, как правило, внешние классы, отслеживающие внешние события WMI, обладают довольно широким дополнительным набором атрибутов, позволяющим легко устанавливать источник этого события.
Также надо заметить, что ключевое слово WITHIN языка WQL, позволяющее подписчику устанавливать интервал опроса экземпляров внутренних классов отслеживания событий, не требуется использовать с внешними классами отслеживания событий.
Асинхронная обработка событий WMI в сценариях WSH
Вернемся к сравнению синхронного и асинхронного способа обработки событий WMI. Синхронная обработка событий – это когда процесс ожидает события и более ничем не занят. Обычно это ожидание – бесконечный цикл проверки условия: поступило событие или нет. В нашем случае в сценарии на листинге 1 для синхронного получения очередного события вызывается метод NextEvent объекта objEvents. Этот метод возвращает сценарию управление только тогда, когда в системе происходит событие, на которое мы подписались.
Асинхронная обработка подразумевает, что сценарий VBScript регистрирует специальный обработчик (подписчик) события в объектной модели WSH и далее продолжает выполнять различные свои задачи. Когда событие возникает, нормальная работа сценария прерывается, запоминается место, где произошло прерывание, а управление передается на зарегистрированный ранее обработчик событий (event sink). После обработки события обработчиком управление возвращается на то действие сценария, которое было прервано.
Для реализации такой техники в объектной модели WSH для сценариев WMI предусмотрен ряд функций, названия которых оканчиваются на суффикс Async. К таким функциям, в частности, относятся: ExecMethodAsync, ExecNotificationQueryAsync, ExecQueryAsync, GetAsync и PutAsync. Эти функции мало чем отличаются от своих синхронных аналогов. Основное их отличие состоит в двух моментах. Во-первых, эти функции ничего не возвращают в вызывающую их программу. Во-вторых, в качестве первого аргумента все они требуют передавать зарегистрированный объект асинхронного подписчика, который создается стандартной функцией CreateObject на базе класса WbemScripting.SWbemSink. Создание этого объекта регистрирует в среде WSH специальный отличительный префикс для процедур-обработчиков событий WMI. Это может быть любая текстовая строка, которая удовлетворяет правилам именования процедур в VBScript. События WMI, которые могут обрабатываться в сценариях WSH, имеют следующие названия – OnCompleted, OnObjectPut, OnObjectReady и OnProgress. Таким образом, все события WMI будут обрабатываться в процедурах, имя которых состоит из зарегистрированного префикса обработчика и названия события. Примеры этого подхода даны в листингах 2, 3.
Листинг 2. Пример сценария асинхронного отслеживания подключения дисковых устройств USB
Sub SINK_OnObjectReady(objReceivedEvent, objAsyncContext)
Wscript.Echo "Name: " & objReceivedEvent.TargetInstance.Name
Wscript.Echo "Caption: " & objReceivedEvent.TargetInstance.Caption
Wscript.Echo "FileSystem: " & objReceivedEvent.TargetInstance.FileSystem
Wscript.Echo "Description: " & objReceivedEvent.TargetInstance.Description
End Sub
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
Set MySink = WScript.CreateObject( _
"WbemScripting.SWbemSink","SINK_")
objWMIservice.ExecNotificationQueryAsync MySink, _
"SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE " & _
"TargetInstance ISA 'Win32_LogicalDisk'" & _
" AND TargetInstance.DriveType = 2"
WScript.Echo "Ожидаем события ..."
While (True)
Wscript.Sleep(1000)
Wend
Листинг 3. Одновременное использование синхронной и асинхронной техники обработки событий на примере Таймера
Dim tCount
Sub TIMER_OnObjectReady(objReceivedEvent, objAsyncContext)
tCount = tCount + 1
Wscript.Echo "Произошло событие Таймера, счетчик tCount=" & tCount
End Sub
strComputer = "."
tCount = 0
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
Set MySink = WScript.CreateObject("WbemScripting.SWbemSink","TIMER_")
objWMIservice.ExecNotificationQueryAsync MySink, _
"SELECT * FROM __InstanceModificationEvent WHERE " & _
"TargetInstance ISA 'Win32_LocalTime'"& _
" AND TargetInstance.Second = 30"
Set objEvents = objWMIService.ExecNotificationQuery("SELECT * FROM Win32_ProcessStartTrace")
Wscript.Echo "Ожидаем запуска новых процессов..."
Do While(tCount < 10)
Set objReceivedEvent = objEvents.NextEvent
WScript.Echo "Новый процесс: PID=" & objReceivedEvent.ProcessID _
& " Name=" & objReceivedEvent.ProcessName
Loop
MySink.Cancel
Обратите внимание на технические особенности сценария, представленного на листинге 3. Асинхронный обработчик внутренних событий таймера, зарегистрированный в этом сценарии, увеличивает переменную tCount на единицу каждые 60 секунд (в тот момент, когда показания секунд на системных часах равны 30) и выводит об этом сообщение. Синхронный обработчик событий запуска процессов внутри цикла вызывает метод NextEvent, который возвращает управление сценарию только после того, как произойдет запуск очередного нового процесса. Становится понятно, что цикл завершится только тогда, когда от начала работы сценария пройдет более 10 минут и в системе будет запущен новый процесс.
Временные и постоянные подписчики на события
Ознакомившись с вышеизложенной частью этой статьи, вы можете решить, что все, что вам необходимо для работы с событиями WMI, вы уже знаете. Теперь вы можете написать любой сценарий, который будет реагировать на подключения съемных запоминающих устройств USB и запустить его при старте компьютера через групповую политику или через системный планировщик задач. Однако все выше рассмотренные нами примеры обладают одним существенным недостатком – события WMI будут отслеживаться только тогда, когда временный подписчик существует, то есть созданный нами сценарий работает. Стоит только остановить сценарий, и обработка событий закончится. В каких то ситуациях такое решение неприемлемо, и необходимо, чтобы события отслеживались постоянно. Для решения этой задачи существуют постоянные подписчики (permanent event consumer). В общем случае постоянный подписчик – это специальный компонент COM+, который постоянно зарегистрирован получать определенные события от службы WMI.
Логика такой регистрации очень проста. У нас есть COM+ компонент, отвечающий за обработку событий – подписчик. У нас есть специальный класс WMI, который называется __EventFilter. Каждый экземпляр объекта класса __EventFilter содержит информацию о том, какие события WMI следует выбирать для передачи какому-нибудь постоянному подписчику. Среди этой информации наиболее важной является WQL-запрос к внутренним или внешним классам отслеживания событий WMI и пространство имен, в котором данный запрос будет выполняться. Для связывания конкретного экземпляра фильтра событий и конкретного экземпляра постоянного подписчика используется специальный связывающий объект WMI, принадлежащий к классу __FilterToConsumerBinding. Этот класс среди своих свойств содержит указатель на постоянного подписчика и указатель на фильтр событий. Таким образом, один и тот же фильтр событий может поставлять события нескольким постоянным подписчикам, а один и тот же постоянный подписчик может получать события от нескольких разных фильтров. Такой подход для связывания хорошо известен разработчикам и администраторам реляционных баз данных для создания связей типа «многие к многим».
Стандартные подписчики на события
Для того чтобы упростить жизнь системных администраторов и избавить их от программирования компонентов COM+, в стандартной инсталляции Microsoft Windows XP и Windows Server 2003 включены пять готовых постоянных подписчиков событий. Эти готовые постоянные подписчики носят название Стандартных Подписчиков (Standard Event Consumers). Все они перечислены в таблице 2.
Таблица 2. Cтандартные подписчики на события WMI (Standard Event Consumers)
Название подписчика
|
Описание
|
|
Служит для выполнения сценария при возникновении указанного события WMI
|
LogFileEventConsumer
|
Служит для записи произвольной строки в текстовый лог-файл
при возникновении указанного события WMI. Не поддерживается
на Windows 2000
|
NTEventLogEventConsumer
|
Служит для записи события в журнал приложений (Application Log) системы при возникновении указанного события WMI.
Не поддерживается на Windows 2000
|
SMTPEventConsumer
|
Служит для отправки почтового сообщения по протоколу SMTP
при возникновении указанного события WMI
|
CommandLineEventConsumer
|
Служит для локального запуска исполняемого файла приложения
при возникновении указанного события WMI. Не поддерживается
на Windows 2000
|
В Microsoft Windows 2000, к сожалению, их только два. Кроме того, один из них ActiveScriptEventConsumer не скомпилирован, поэтому прежде чем его использовать необходимо скомпилировать командой:
mofcomp %windir%\wbem\Scrcons.mof
Классы стандартных подписчиков на события WMI в ОС Windows XP Service Pack 2 и Windows Server 2003 определены в специальном пространстве WMI-имен root\subscription. Там же рекомендуется создавать экземпляры этих подписчиков, фильтры событий и объекты связывания подписчиков и фильтров событий.
Создание MOF-файла для подписки на события и его компиляция
Для того чтобы воспользоваться стандартными подписчиками на события WMI, вовсе не обязательно писать сценарии VBScript. Для этого достаточно взять в качестве шаблона приведенный на листинге 4 MOF-файл и немного модифицировать его под себя. Далее достаточно просто скомпилировать его штатной утилитой mofcomp – и все готово.
Листинг 4. Файл NTLog-for-remdev.mof для регистрации постоянного подписчика событий
// Создает экземпляр объекта стандартного подписчика NT Event log и обозначает его псевдонимом $CONSUMER
#pragma namespace ("\\\\.\\root\\subscription")
instance of NTEventLogEventConsumer as $CONSUMER
{
Name = "MyStandardConsumer"; // Уникальное имя экземпляра
SourceName = "MyMonitoring"; // Системный источник события
EventID = 235 ; // Номер события в журнале приложений
EventType = 3; // Нумеротор EVENTLOG_INFORMATION_TYPE
Category = 0; // Категорию сообщения мы не определяем (0)
NumberOfInsertionStrings = 1; // Число текстовых строк в параметре InsertionStringTemplates
InsertionStringTemplates = { "Removable Drive connected under "
"drive letter %TargetInstance.Caption% with file system %TargetInstance.FileSystem%" };
};
// Создание экземпляра объекта фильтра событий и присвоение ему псевдонима $FILTER
instance of __EventFilter as $FILTER
{
Name = "MyRemDevFilter"; // Уникальное имя экземпляра
// Определяем запрос WQL для отбора интересующих нас событий
Query = "SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA "
"\"Win32_LogicalDisk\" AND TargetInstance.DriveType = 2" ;
QueryLanguage = "WQL"; // Определяем язык запроса
EventNamespace = "root\\CIMv2"; // Определяем пространство имен, где будет выполняться запрос
};
// Создаем экземпляр объекта связывания между фильтром и подписчиком,
// используя их временные псевдонимы
instance of __FilterToConsumerBinding
{
Consumer = $CONSUMER;
Filter = $FILTER;
};
Создайте файл с именем NTLog-for-remdev.mof и заполните его в соответствии с листингом 4. Затем выполните синтаксическую проверку файла командой:
mofcomp –check NTLog-for-remdev.mof
Данный MOF-файл создает экземпляр стандартного подписчика на события WMI, который осуществляет запись информационного события с кодом 235 в журнал приложений системы (Application Log) в случае, если в системе появляется новое съемное запоминающее устройство типа USB FlashDrive. В данные события вносится информация о букве диска, которая была назначена этому съемному устройству, а также тип файловой системы, обнаруженной на подключенном съемном устройстве.
Для того чтобы созданный нами MOF-файл был импортирован в репозиторий WMI и события подключения съемных дисковых устройств типа USB FlashDrive стали регистрироваться в журнале приложений, нам необходимо скомпилировать этот MOF-файл. Для этого с правами администратора выполните команду:
mofcomp NTLog-for-remdev.mof
Если после выполнения команды вы увидите сообщение «Storing data in the repository... Done!», то это означает, что операция прошла успешно. Теперь проверьте журнал приложений («Event Viewer Application») на предмет ошибок от службы WinMgmt. Скорее всего, их не будет. Если же вы увидите запись об ошибке с кодом 10 (EventID 10), в тексте которого будет присутствовать информация об ошибке WMI номер 0x80041003 [5], то знайте, что той учетной записи, под которой вы проделали компиляцию MOF-файла, недостаточно прав для активации фильтра событий. Для решения этой проблемы выполните все эти действия из-под локальной встроенной учетной записи администратора. Это ситуация довольно редкая, но она случается с доменными учетными записями, включенными в группу локальных администраторов при отсутствии подключения к контроллеру домена. Рассматривать детально причину такого провидения системы мы сейчас не будем. Просто помните об этом.
После того как вы подключите к компьютеру съемное дисковое устройство, в журнале событий вы увидите событие с текстом примерно следующего содержания:
Event ID: 235
Source: MyMonitoring
Type: Information
Description:
The description for Event ID ( 235 ) in Source ( MyMonitoring ) cannot be found.
The local computer may not have the necessary registry information or message DLL files
to display messages from a remote computer. You may be able to use the /AUXSOURCE= flag
to retrieve this description; see Help and Support for details. The following information
is part of the event: Removable Drive connected under drive letter E: with file system FAT32.
|
Давайте теперь немного подробнее разберем приведенный пример MOF-файла. Обратите внимание, что синтаксис MOF файлов [6] очень похож на синтаксис языка программирования C++. Первая значащая строка определяет пространство имен WMI, где будут размещены создаваемые нами экземпляры объектов. Затем идет описание экземпляра объекта класса стандартного подписчика NTEventLogEventConsumer и присвоение ему псевдонима (alias) для последующей ссылки на него. При определении экземпляра этого объекта мы задаем его свойства – это самое интересное. Мы определяем уникальное имя этого экземпляра стандартного подписчика, код события, которое будет вносить в журнал приложений этот экземпляр стандартного подписчика, тип события (0 – SUCCESS, 1 – ERROR, 2 – WARNING, 3 – INFORMATION, 4 – AUDIT_SUCCESS и 5 – AUDIT_FAILURE), источник события и его категорию. Далее мы определяем число текстовых строк, которые будут передаваться службе журнала Windows при создании события с указанным нами кодом. Для всех событий в системе обычно предусмотрены текстовые шаблоны для поля Description. Это помогает сильно экономить место в журналах событий системы. Эти шаблоны обычно содержат специальные поля для внесения текстовых строк с деталями о конкретном событии. Поскольку для нашего события такого шаблона не существует, то в поле описания события (Description) об этом будет записано предупреждение.
Далее в MOF-файле создается экземпляр объекта класса __EventFilter, в котором описываются те события, на которые мы хотим назначить постоянный подписчик. Мы, как и раньше, определяем уникальное имя этого экземпляра и задаем остальные свойства. Среди этих свойств мы определяем запрос WQL, отслеживающий интересующие нас события и пространство имен WMI, в котором этот запрос следует выполнять.
Наконец последнее, что мы определяем в этом MOF-файле – это связывающий объект, который собственно и отвечает за передачу событий от фильтра к подписчику.
Как описано выше, существует пять классов стандартных подписчиков (см. таблицу 2). Соответственно помимо записи событий в журнал событий мы можем, например, запускать сценарий VBScript как реакцию на интересующее нас событие WMI или приложение из командной строки. Давайте посмотрим, как очень легко можно модифицировать наш пример со стандартным подписчиком, оставляющим в журнале событий системы (Event Log) свою запись о подключении съемного запоминающего устройства, на запуск сценария VBScript.
Для начала давайте создадим сценарий, который мы бы хотели запускать при возникновении интересующих нас событий WMI. Я не буду подробно разбирать особенности этого сценария, так как это не соответствует тематике нашей статьи. Обозначу лишь один важный момент: когда данный сценарий WSH запускается ядром системы WMI, в объектное окружение WSH добавляется специальный объект TargetEvent, который по набору свойств соответствует тому, что возвращала бы функция NetEvent при синхронной обработке уведомляющих запросов в наших примерах выше.
Пример нашего сценария дан в листинге 5:
Листинг 5. Код из файла script.vbs. Файл должен находиться в папке c:\windows\system32\wbem
Dim objFSO, objFile
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("C:\ScriptLog.txt", 8, true)
objFile.WriteLine "Time: " & Now & "; Entry made by: ASEC"
objFile.WriteLine "Script Started for USB Drive: " & TargetEvent.TargetInstance.Caption
objFile.Close
Поместите этот сценарий в папку c:\windows\system32\wbem. В принципе его можно хранить и в любой другой папке, но вы должны понимать, что при наличии соответствующих прав на файл пользователь может изменить содержимое VBS-сценария, а поскольку он выполняется в контексте безопасности SYSTEM, то пользователь может поднять свои права на локальной машине и натворить еще много других бед. Рекомендуемая мной папка имеет достаточно надежные разрешения доступа к файлам по умолчанию и позволит избавиться от этой потенциальной уязвимости.
Теперь пришел черед модифицировать наш MOF-файл. На листинге 6 дано определение экземпляра объекта стандартного подписчика, ответственного за запуск сценариев. Как вы уже могли убедиться, все довольно просто. Мы, как и раньше, определяем уникальное имя экземпляра класса стандартного подписчика и задаем другие его свойства. Замените в нашем MOF-файле (см. листинг 4) описание стандартного подписчика на это новое описание.
Листинг 6. Пример кода MOF-файла для создания экземпляра стандартного подписчика, выполняющего сценарий из указанного текстового файла
instance of ActiveScriptEventConsumer as $CONSUMER
{
Name = "MyStandardConsumer";
KillTimeout = 300;
ScriptingEngine = "VBScript";
ScriptFileName = "c:\\windows\\system32\\wbem\\script.vbs";
};
Наиболее важным для нас свойством является имя файла сценария, который следует исполнять в качестве реакции на события. Смысл всех остальных свойств вполне очевиден из их названия. Рассмотренная проблема с размещением файла сценария может быть решена еще одним способом. Зачем вообще сохранять сценарий на диск машины, где он может быть модифицирован пользователем, когда можно сохранить его код прямо в репозитории WMI. Конечно, в каких-то случаях этот способ может быть не очень удобным, однако я специально привел его пример на листинге 7, поскольку этот способ хранения сценария реакции на события априори более безопасный.
Листинг 7. Пример кода MOF-файла для создания экземпляра стандартного подписчика, выполняющего сценарий из сохраненного в репозитории WMI кода VBScript
instance of ActiveScriptEventConsumer as $CONSUMER
{
Name = "MyStandardConsumer";
ScriptingEngine = "VBScript";
KillTimeout = 300;
ScriptText =
"Dim objFSO, objFile\n"
"Set objFSO = CreateObject(\"Scripting.FileSystemObject\")\n"
"Set objFile = objFSO.CreateTextFile(\"C:\\ScriptLog.txt\", 8, true)\n"
"objFile.WriteLine \"Time: \" & Now & \"; Entry made by: ASEC2\"\n"
"objFile.WriteLine \"Script Started for "
USB Drive: \" & "
"TargetEvent.TargetInstance.Caption\n"
"objFile.Close\n";
};
Обратите внимание, что сценарий VBScript задается в виде текстовой строки, как в языке C++, и для перевода строк используется спецсимвол «\n», а для включения в текст двойных кавычек и обратных наклонных черт – спецсимволы «\"» и «\\» соответственно.
После того как вы проделали изменение нашего базового MOF-файла, еще раз проверьте синтаксис MOF-файла и компилируйте его, так как описано выше командой mofcomp. После этого подключите к системе съемное запоминающее устройство USB FlashDrive и убедитесь, что в корне диска C: создался файл отчета ScriptLog.txt о работе нашего сценария с примерно следующим содержимым:
Time: 20.03.2006 12:43:18; Entry made by: ASEC
Script Started for USB Drive: E:
|
Отмена регистрации постоянных подписчиков
После того как вы проделали упражнение с созданием постоянного стандартного подписчика на события WMI, этот подписчик будет работать в системе даже после ее перезагрузки. У пытливого читателя сразу же возникнет вопрос: «Как же теперь отменить все результаты наших экспериментов?» Эта задача решается довольно просто.
Для того чтобы отменить созданную нами постоянную регистрацию стандартных подписчиков, следует выполнить приведенный на листинге 8 сценарий VBScript.
Листинг 8. Отмена регистрации постоянного стандартного подписчика для NTEventLogEventConsumer
On Error Resume Next
Set objWIMService = GetObject("winmgmts:\\.\root\subscription")
Set objList = objWIMService.ExecQuery("references of {__EventFilter.Name='MyRemDevFilter'}")
For each objInst in objList
objInst.Delete_
Next
Set objList = objWIMService.ExecQuery"references of {NTEventLogEventConsumer.Name='MyStandardConsumer'}")
For each objInst in objList
objInst.Delete_
Next
Set objSTDConsumerInst = GetObject("winmgmts:\\.\root\subscription:NTEventLogEventConsumer='MyStandardConsumer'")
objSTDConsumerInst.Delete_
Set objEventFltInst = GetObject("winmgmts:\\.\root\subscription:__EventFilter='MyRemDevFilter'")
objEventFltInst.Delete_
Этот сценарий использует технику запроса WQL с ключевым словом references of для нахождения всех экземпляров связывающих объектов класса __FilterToConsumerBinding привязанных к экземпляру фильтра событий с именем NTLogConsumerForRemDevFilter и к экземпляру стандартного подписчика с именем NTLogConsumerForRemDev. После нахождения всех этих связывающих объектов он удаляет их, а затем удаляет и сами экземпляры объектов стандартного подписчика и фильтра событий.
Просмотр и модификацию регистрации постоянных подписчиков на события WMI также удобно осуществлять с использованием утилиты WMI Event Registration из комплекта WMI Administrative Tools [7].
Полезные примеры мониторинга системных событий
Перед заключением приведу пару простых, но часто используемых практических примеров отслеживания событий WMI. В целях упрощения эти примеры приведены в виде сценариев синхронной обработки событий на VBScript, однако вы уже хорошо понимаете, как их можно легко модифицировать к асинхронному виду или применить для их использования механизм стандартных подписчиков.
Для сравнения один из них, приведенный на листинге 9, использует внутренние (intrinsic) события и внутренние классы отслеживания событий WMI, а второй, приведенный на листинге 10, использует внешние (extrinsic) события, генерируемые провайдером WMI StdRegProv.
Листинг 9. Пример синхронного отслеживания событий с файлами TXT в папке c:\temp
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMv2")
Set objEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceOperationEvent WITHIN 5 WHERE " & _
"TargetInstance ISA 'CIM_DataFile'" & _
" AND TargetInstance.Drive = 'c:'" & _
" AND TargetInstance.Extension = 'txt'" & _
" AND TargetInstance.Path = '\\temp\\'")
Wscript.Echo "Ожидаем события ..."
Do While(True)
Set objReceivedEvent = objEvents.NextEvent
WScript.Echo "Filename: " & objReceivedEvent.TargetInstance.FileName
WScript.Echo "Name: " & objReceivedEvent.TargetInstance.Name
Loop
В примере на листинге 9 сценарий, используя синхронную технику, отслеживает события создания удаления и переименования файлов с расширением TXT в папке C:\TEMP. Если вы будете экспериментировать с этим сценарием, будьте аккуратны, не увлекайтесь расширением области мониторинга за счет захвата большего числа файлов и папок. В современных версиях OC Windows изменения в файловой системе происходят очень часто, и ядро WMI может не выдержать потока всех свалившихся на него событий и заберет почти 100% процессорного времени системы на их обработку.
Следующий пример, приведенный на листинге 10, показывает пример синхронной техники обработки внешних (extrinsic) событий WMI для провайдера StdRegProv.
Листинг 10. Пример синхронного отслеживания событий изменения значения в реестре
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\DEFAULT")
Set objEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM RegistryValueChangeEvent WHERE " & _
"Hive = 'HKEY_LOCAL_MACHINE'" & _
" AND KeyPath = 'SOFTWARE\\MyKey\\MySubKey\\'" & _
" AND ValueName = 'MyValue'")
Wscript.Echo "Ожидаем события ..."
Do While(True)
Set objReceivedEvent = objEvents.NextEvent
Wscript.Echo "Произошло событие RegistryValueChangeEvent."
Loop
Поскольку пример довольно прост, мы не будем сейчас останавливаться на его внимательном рассмотрении и оставим это на вторую часть статьи, где среди прочего будет подробно рассматриваться работа с провайдером WMI для доступа к реестру.
Что еще есть на вооружении WMI?
Дополнительные возможности WMI не исчерпываются только обработкой событий и созданием постоянных стандартных подписчиков. В следующей части статьи мы поговорим о практическом использовании основных провайдеров WMI и приемах работы с ними. Среди этих провайдеров я особо хотел бы отметить следующие: Win32_Ping – осуществляющий формирование ICMP Echo-запросов к указанным хостам сети, Win32_NTEventLog – с которым мы частично знакомы и который обеспечивает доступ к журналам событий системы OC Windows. Также мы рассмотрим провайдер работы с реестром – StdRegProv и провайдер взаимодействия с Microsoft Active Directory. Кроме них, будет полезно обсудить провайдер для работы со счетчиками производительности и довольно простой вопрос, который, однако, вызывает частенько сложности, – работа с датами и временем через WMI.
Ссылки, литература:
- Леонтьев К. Вы все еще не используете WMI? Часть I. – Журнал «Системный администратор», №1, январь 2006 г. – 4-11 с. (http://www.samag.ru/cgi-bin/go.pl?q=articles;n=01.2006;a=02);
- Леонтьев К. Вы все еще не используете WMI? Часть II: пишем сценарии. – Журнал «Системный администратор», №2, февраль 2006 г. – 6-14 с. (http://www.samag.ru/cgi-bin/go.pl?q=articles;n=02.2006;a=02)
- Борн Г. Руководство разработчика на Microsoft Windows Scripting Host 2.0. Мастер класс. // Пер. с англ. – СПб.: Питер; М.: ИТД «Русская редакция», 2001. – 480 стр.: ил.
- Microsoft Windows Script 5.6 Documentation – http://www.microsoft.com/downloads/details.aspx?FamilyId=01592C48-207D-4BE1-8A76-1C4099D7BBB9&displaylang=en.
- WMI Code Creator 1.0 – http://download.microsoft.com/download/0/c/a/0ca7691c-6335-4143-8f9f-6708969f8212/WMICodeCreator.zip.
- Таблица кодов ошибок WMI – http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/wbemerrorenum.asp.
- Синтаксис MOF-файлов – http://msdn.microsoft.com/library/en-us/wmisdk/wmi/managed_object_format.asp?frame=true.
- WMI Administrative Tools – http://download.microsoft.com/download/.NetStandardServer/Install/V1.1/NT5XP/EN-US/WMITools.exe.