WMI. Wладение Mагической Iнформацией (ч. 2)

Автор: Виталий Белик

Ну, да ладно. Мы же пойдем ровным путем, дающим краткое, но четкое представление о том, как будет работать система. Заранее раскрывая карты, скажу, что с Делфийском коде обращение к записям и полям таблицы будет похоже на обращение к ячейкам массива. 

Двумерного разумеется. На С++:

TWMIRecord* TWMI::Item(int i){
  TWMIRecord *r=0;
  // Инициируем итератор для списка
  list<twmirecord>::iterator k;
  // Пройдемся циклом по списку пока не дойдем
  // до указанной по номеру записи
  for(k=RecList.begin();(k!=RecList.end())&&(i>0);k++,i--);
  // Если записть такая нойдена, в том смысле
  // что индекс запрошенной записи
  // не вылезает за пределы списка
  // то вернем объект из списка
  if(k!=RecList.end()&&i>=0){
    r=&*k;
  }
  return r;
};

Здесь единственный бок – я не знаю, как проверить выход за пределы списка указанного номера запрошенной записи, и так же не знаю другого способа получить по номеру из списка без прохода циклом. Поэтому я решил просто написать проход циклом по списку, пока не конец, или пока нужный номер записи в списке не достигнут. В принципе это работает, так что пусть так и остается.

Ладушки. Пора приступать к описанию второго класса, класса отвечающего за обработку записи TWMIRecord (на Делфи): 

TWMIRecord=class
 private
  // Список полей и их значений
  FFields:TStringList;
  // Процедура получения данных из полей записи
  Procedure Enum(Obj:OleVariant);
  Constructor Create;
  Destructor Free;
    function GetItem(v: Variant): String;
 public
  Path:String;
  // Функция возвращающая число полей
  Function HighRecordIndex:Integer;
  // Функция, возвращающая имя i-того поля
  Function FieldName(i:integer):String;
  // Свойство получающее значение определенного поля
  Property Item[v:Variant]:String read GetItem; default;
 end;

Здесь особое внимание должно быть уделено процедуре Enum(), которая, приняв объект-запись от энумератора записей, прокатится по ее полям, выделив ее значения в свой список, и свойство Item, объявленное по умолчанию. Оно может принимать как строку – при этом получить значение по имени поля, так и число, чтоб получить значение из поля по определенному номеру. Это дает серьезную мобильность, можно в цикле пройтись по полям, а можно просто получить значение по имени поля (на С++):

struct sField{
  // Имя поля
  wstring Name;
  // Значение поля, переведенное в строку
  wstring Value;
};

class TWMIRecord
{
private:
  // Свойство, принимающее значение поля
  VARIANT Prop;
  // Обьект-записи полученный от провайдера
  IWbemClassObject *Obj;
  wstring FString;
  // Список полей и их значений
  list<sfield> FFields;
  // Количество полей
  int CountList;
public:
  TWMIRecord(IWbemClassObject *AObj);
  ~TWMIRecord(void);
  // Метод, получающий поле по имени
  sField Field(wstring AName);
  // Метод получающий поле по номеру
  sField Field(int iName);
  // Функция, получающая верхний индекс в списке полей
  int high();
};

Кто-то скажет: «А зачем Field(wstring AName) возвращает структуру, достаточно ведь вернуть значение?». Верно, но вдруг захочется пополнить структуру еще какой-нибудь характеристикой поля, например, типом, так что пусть этот метод возвращает всю структуру – ничего пагубного в этом нет. Ок. Реализуем этот класс (на Делфи):

{ TWMIRecord }

constructor TWMIRecord.Create;
begin
 // Создается класс списка полей и их значения
 FFields:=TStringList.Create;
 // Здесь я не предполагал хранить ничегокроме имя и значения поля
 // так что TString'a вполне хватит
end;

// Метод проходя по полям
procedure TWMIRecord.Enum(Obj: OleVariant);
var PropEnum:IEnumVariant; i:Cardinal; s:string; d:double;
begin
    // Приготовим список для внесения в него данных
    FFields.Clear;
    // Инициализируем энумератор
    PropEnum:=IEnumVariant(IUnknown(Obj.Properties_._NewEnum)); //Поля
    // и начнем по нему лазить, пока он выбирает записи
    while (PropEnum.Next(1, Obj, i) = S_OK) do begin
     try
      // Здесь я прикрутил распознавание типа даты
      // если поле содержит дату, то привести ее в
      // понятный человеку вид
      if obj.CIMType=$00000065 then begin
       s:=Obj.Value;
       s:=copy(s,7,2)+'.'+copy(s,5,2)+'.'+copy(s,1,4);
      end else
      // если же это не дата, то пусть сама программа приводит
      // значение к строке. в противном случае в строку
      // должно писаться значение [NULL], если такое поле пусто
      if VarIsNull(Obj.Value) then s:=WMIValueNull else s:=Obj.Value;
      FFields.Values[Obj.Name]:=s;
     except

     end;
    end;
end;

function TWMIRecord.FieldName(i: integer): String;
begin Result:='';
 // если номер попадает в список полей, вернем имя поля
 // по его номеру
 if (i>=0)and(i<FFields.Count) then
  Result:=FFields.Names[i];
end;

destructor TWMIRecord.Free;
begin
 // Освободим список, уберем мусор
 FFields.Free;FFields:=nil;
end;

function TWMIRecord.GetItem(v: Variant): String;
begin
 Result:='';
 // Если мы хотим получить значение по номеру поля
 // нужно проверить, не выходит ли указанный индекс
 // за пределы списка полей
 if VarIsOrdinal(v)and(v>=0)and(v<FFields.Count) then
  // И если не выходит - вернуть это поле
  Result:=FFields.Values[FFields.Names[v]];

 // если же мы хотим получить значение поля по имени
 if VarIsStr(v) then
 // мы просто передаем имя, и если
 // поле с таким именен есть возвращается его значение
  Result:=FFields.Values[v];
 // иначе вернется пустая строка
end;

function TWMIRecord.HighRecordIndex: Integer;
begin
// Последний индекс в списке полей
Result:=FFields.Count-1;
end;

Что тут добавить еще? Энумератором получаем поля и их значения, раскладывая в «массив». И даем возможность выбирать из этого массива в самой программе, любым способом, по индексу или по имени. И еще константа const WMIValueNull='[NULL]’. На С++:

TWMIRecord::TWMIRecord(IWbemClassObject *AObj)
{
  Obj=AObj;
  HRESULT hres;
  BSTR pstrName;
  VARIANT pVal;
  CIMTYPE pvtType;
  sField field;
  // Начинаем перечисление
  hres=Obj->BeginEnumeration(0);
  CountList=0;
  // Если это возможно конечно, проходимся циклом
  // пока не нарвемся на ошибку, или пока энумератор не
  // выберет все данные
  while(!FAILED(hres)&&(hres!=WBEM_S_NO_MORE_DATA)){
    // Получим очередное "следующее" поле
    hres=Obj->Next(0,&pstrName,&pVal,&pvtType,0);
    // Если оно удачно получено
    if(!FAILED(hres)&&(hres!=WBEM_S_NO_MORE_DATA)){
            field.Name.clear();
      // Запишем его имя
      field.Name=wstring(_bstr_t(pstrName,false));
      // Если его значение не пусто
      if(pVal.vt!=VT_NULL){
        field.Value.clear();
       // получим его, преобразовав в строку в зависимости
       // от типа
        switch(pvtType){
          case wbemCimtypeBoolean:
             field.Value=(pVal.boolVal)?L"TRUE":L"FALSE";
             break;
          case wbemCimtypeString:
             field.Value=wstring(_bstr_t(pVal.bstrVal,false));
             break;
          case wbemCimtypeSint32:
             int i=pVal.intVal;
             char c[30]="";
             itoa(i,c,10);
             for(int i=0;i<10&&c[i]!=0;i++){field.Value+=c[i];}
             break;
        }
      }
      // и внеся новое поле в наш список
      FFields.push_back(field);
      // увеличим счетчик количества полей
      CountList++;
    }
  }

}

int TWMIRecord::high()
{
  // Вернем верхний индекс списка полей
  return CountList-1;
}

TWMIRecord::~TWMIRecord(void)
{
  // Освободим список
  FFields.clear();
}

sField TWMIRecord::Field(wstring AName){
  FString=L"";
  sField res={L"",L""};
  // Приготовим итератор для прохода по списку
  list<sfield>::iterator i=FFields.begin();
  // пока не конец списка
  for(;i!=FFields.end();i++){
    // получим очередной элемент
    sField ws=*i;
    // проверим не совпадает ли имя поля
    // полученного элемента с указанным нами
    if(ws.Name==AName){
      // Если совпадает  - выйдем из цикла
      res=*i;
      break;
    }
  }
  return res;
};

sField TWMIRecord::Field(int iName){
  FString=L"";
  sField res;
  // Приготовим итератор для прохода по списку
  list<sfield>::iterator i;
  // пока не конец списка или не достигнут указанный индекс поля
  for(i=FFields.begin();(i!=FFields.end())&&(iName>=0);i++,iName--);
  // Если список полей весь пройден а индекс поля еще не достигнут
  // вернем пустые строки.
  if(i!=FFields.end() && iName>=0){ res.Name=L"";res.Value=L"";}
  // Иначе вернем данные из списка
  else {res=*i;}
  return res;
};

**** Комментарий автора.

Здесь стоит отдельно оговорить – преобразование полученного поля при проходе по полям в конструкторе. Я ничего другого не придумал, как банальное использование старой, доброй itoa(), а после преобразования посимвольный внос из массива символов в юникодовую строку. Надеюсь, все обратили внимание, что в Си я использовал именно юникодовые строки для хранения значений?

Опять-таки, итерации по списку – в Делфи, дяди из Борланда дали возможность обращаться к элементам списка как к массиву, как сделано в STL я не знаю, поэтому банально – прошелся циклом по list’y. :)

Так… Вроде ничего не забыли описать? Если нет, то пора попробовать эту махину в действии.

Starting Line

Начнем, пожалуй, с Делфи. Создадим проект с формой, на него кинем StringGrid, и в обработчике создания формы OnCreate напишем такой код (на Делфи):

procedure TForm1.FormCreate(Sender: TObject);
var w:TWMI;i,j:integer; wr:TWMIRecord;
begin
 // Создадим обьект WMI
 w:=TWMI.Create(nil);
 // Выкатим ему запрос
  w.SQL:= "SELECT caption, CommandLine FROM Win32_Process";
  with StringGrid1 do begin
 // Развернем Грид на нужно е количество записей
    RowCount:=w.HighObject+1;
    FixedCols:=0;
    // В цикле пройдясь по записям
    for i:=0 to w.HighObject do begin
     wr:=w[i];
     if ColCount<(wr.HighRecordIndex+1) then ColCount:=(wr.HighRecordIndex+1);
     // впишем значения полей в таблицу
     for j:=0 to wr.HighRecordIndex do begin
      Cells[j,i]:=wr[j];
     end;
    end;
  end;
 w.Free;
end;

Перед этим не забудем создать в проекте Unit (назовем его Unit2), и вложить в него код классов, не забыв в нем указать необходимые модули для работы механизма в описанный классах uses Classes,contnrs,variants,ActiveX,Comobj; Не забыв указать в разделе uses модуля формы этот самый Unit2.

Теперь код обработчика на форме связан с модулем, где описаны классы.

Если все проделано правильно после жмака по F9 на экран выкатится форма со списком процессов (см. рисунок 1):

Ну вот. Запрос SELECT caption, CommandLine FROM Win32_Process получил набор с заголовками запущенных в целевой системе процессов, и путями к файлам этих процессов. Администратору сразу видно, что запущено у пользователя, не нужно идти к нему, или использовать платные средства удаленного администрирования. Увы, не все они могут показать такую информацию, а иногда это важно для понимания состояния системы.

А теперь тоже самое но на С++. Лукаво не мудрствуя, сделаем это в консоли, это попроще будет:

#include "stdafx.h"
#include "TWMI.h"
#include <locale>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
  // Включим русский язык для консоли
  setlocale(LC_ALL,"russian");
  // Создадим класс WMI
  TWMI *wmi=new TWMI();
  // Скормим ему запрос
  if(wmi->SetQuery("SELECT caption FROM Win32_Process")){
    // Если запрос удачно обработал
    printf("Получили данные\n");
    int i=0;
    // В цикле пройдемся по записям,
    for(TWMIRecord *r=wmi->Item(i);r;i++,r=wmi->Item(i)){
      // И выведем значение поля
      wcout<<r->Field(L"Caption").Value<<'\n';
    }
  } else {printf("Неудача");}
  // после чего уберем мусор
  delete wmi;
  getchar();
  return 0;
}

Здесь тоже самое, разве что я изменил запрос. Попросил только заголовки процессов. В консоли красиво не выведешь. Здесь же применен метод получения поля по его имени, но с таким же успехом его можно заменить на:

for(int i=0;i<r->high();i++){
   sField f=r->Field(i);
   wcout<<f.Name<<'='<<f.Value<<'\n';
} wcout<<'\n';

Где в цикле будет вестись проход по всем полям по их номеру. Запустим и посмотрим, что же получилось (см. рисунки 2, 3):

Запрос на рисунке 2 показывает, что все хорошо прошло. Объект получил от провайдера WMI информацию, и программа вывела ее на экран. На втором же рисунке показана возможность показа всех полей, одна запись тут разделена пустой строкой. Вообще я в запросе указал два поля CommandLine, но провайдер предоставляет кроме этого еще несколько, судя по всему стандартных полей, характеризующих тип самого запроса. Например, свойство __PROPERTY_COUNT=2 говорит о том, что мы запросили два поля, между прочим, им можно пользоваться, чтоб узнать количество полей. А __CLASS=Win32_Process говорит о том, какое «представление» использовалось. Мы хотели получить список процессов – вот и получили, соответственно и класс Win32_Process. Ну и, конечно же, в конце данные о тех самых наших полях, которые мы запрашивали, Caption и CommandLine. Все работает замечательно.

Теперь можно самостоятельно написать свою «тулзу» для удаленного администрирования. Учитывая, что классов в WMI много, можно много чего узнать о компьютерах в сети. Например, можно узнать, что за пользователи описаны в компьютере, выкатив запрос SELECT * FROM Win32_Account (см. рисунок 4):

Или предположим звонит юзер:

-у меня материнка с ума сошла, дайте дрова!

-какая у вас материнка?

-пластиковая, из магазина…

Да-да. Это не анекдот. Юзеры, иногда в силу своей компьютерной неграмотности, такое откалывают, что Задорнов «плякаль». А, запросив SELECT * FROM Win32_BaseBoard у виндоуса того пользователя можно увидеть, что у него (см. рисунок 5): 

Оказывается Асус. О! И даже серийник есть. Ну, теперь просто запросить на сайте производителя дровишки для P5GZ-MX и удаленно RAdmin’ом например проинсталлировать. Кто-то скажет: «Так, а чего самим РАдмином или типа него не посмотреть денить в свойствах?» Где? Все знают, где информация такая лежит? Ану-ка, партизаны: «шнель шпрехен… ». Я лично не знаю, где можно посмотреть такие подробности. А тут раз – и все как на ладони. А тем паче, своя Наташка… ))))

Post Scriptum

Ну вот. Собственно, на этом можно поставить точку. А можно и троеточие, ибо WMI помимо получения информации позволяет управлять компьютером. Опять-таки теми же запросами. «Тушить процессы» или выключать компьютер удаленно.

Например, если вызвать метод Terminate класса, полученного по запросу на Win32_Process, то можно потушить все процессы из запроса. Порывшись в MSDN, можно даже найти пример [2]. Если ссылку еще не завалили, посмотрите, как это делается: Запросом получается набор объектов, у которых вызывается метод Terminate, и они тушатся.

В общем, все это очень обнадеживает, и, если системный администратор владеет этим каратэ – цены ему нет. Домен свой он будет держать атланту подобно. Так что рекомендую вляпаться в эти микрософтовские катакомбы – не пожалеете.

Вы можете ответить или разместить запись на вашем сайте.

Ответить

Powered by Procoder