Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4413.html
 

а вы знакомы с WMI?

Опубликовано 23.04.2010 г. 00:44

несколько дней назад аббревиатура WMI мне была знакома лишь из списка служб Windows. Однако после короткого знакомства выяснилось что это весьма мощный инструмент для администрирования и получения информации о системе, причем удаленных системах в том числе. В прочем в работе с удаленными машинами это насколько я понимаю и заключается суть WMI. Меня же пока что заинтересовало лишь получение информации с локальной машины, хотя на самом деле получить информацию с удаленной ни чуть не сложнее должно быть. Итак, маленький пример использования WMI в Delphi. Чтобы получить возможность использовать WMI в Delphi необходимо импортировать библиотеку Microsoft WMI Scripting v1.x Library (wbemdisp.tbl) после чего непосредственно получим модуль с описанием интерфейсов WBEM (web-based enterprise managment). Поскольку навыков работы с WMI  я не имею, то теории здесь не будет никакой (: Допустим мы хотим получить некоторую информацию о конфигурации системы, например, о процессоре, об ОС, о BIOS, о жестком диске, или посмотреть список процессов, или даже данные о производительности. Для решения подобных задач сначала необходимо подключиться к провайдеру WMI и выбрать нужную информацию опираясь на описание классов предоставляемых WMI. Для начала с помощью ISWbemLocator нам требуется подключиться к нужной машине и пространству имен, и получить экземпляр ISWbemServices с помощью которого можно выполнять действия над этим пространством имен, например выполнять запросы, для запросов используется язык WQL, весьма напоминающий SQL. Итак, services для будущих нужд занесем в private секцию формы, и получим его экземпляр при создании формы.

uses WbemScripting_TLB;
...
  private
     services : ISWbemServices;
...
procedure TMainForm.FormCreate(Sender: TObject);
var locator : ISWbemLocator;
begin
    locator := CoSWbemLocator.Create();
    services := locator.ConnectServer('.','root\cimv2','','','','',0,nil);
end;

Метод ConnectServer содержит в качестве параметров имя (ип адрес) машины, имя пользователя пароль и т.п Допустим мы хотим получить сведения об операционной системе которые представлены классом Win32_OperatingSystem, например название, номер сборки, архитектуру, директорию системы; Информацию о процессоре Win32_Processor: название, тактовая частота в МГц, число ядер, размер кэша второго уровня в килобайтах; информацию о видеоадаптере (Win32_VideoController): название, количество памяти, режим экрана, драйверы. для хранения нужных классов и полей воспользуется sl:TStringList, в значения которого запишем sl[wmi_class_name] = field_list; Далее для всех записей из списка извлечем нужную информацию с помощью sevices. Далее разделим с помощью pl : TStringList список извлекаемых для класса полей на отдельные строки. Для каждой такой строки получим соответствуещее свойство по названию, и добавим в итоговую информацию в TValueListEditor на форме. После чего для наглядности переведем объем видеопамяти в Мбайты. Поэтому расширим обработчик создания формы следующим образом:

procedure TMainForm.FormCreate(Sender: TObject);
var locator : ISWbemLocator;
    objectSet : ISWbemObjectSet;
    obj : ISWbemObject;
    sl,pl : TStringList;
    i,j:integer;
    wmiClass, selectList : string;
    propName : string;
    propValue : string;
    varProp : oleVariant;
    intVal : cardinal;
begin
    locator := CoSWbemLocator.Create();
    services := locator.ConnectServer('.','root\cimv2','','','','',0,nil);
    locator := nil;

    sl := TStringList.Create;
    pl := TStringList.Create;

    sl.Values['Win32_OperatingSystem'] := 'Caption, Version, OSArchitecture, WindowsDirectory';
    sl.Values['Win32_Processor']       := 'Name, NumberOfCores, CurrentClockSpeed, L2CacheSize';
    sl.Values['Win32_VideoController'] := 'VideoProcessor, AdapterRAM, VideoModeDescription, InstalledDisplayDrivers';
    for i:=0 to sl.Count - 1 do begin
        wmiClass  := sl.Names[i];
        selectList := sl.ValueFromIndex[i];
        pl.CommaText := selectList;
        objectSet :=services.ExecQuery('select '+ selectList + ' from ' + wmiClass,'WQL',
                                   wbemFlagReturnImmediately and wbemFlagForwardOnly,nil);
        obj := objectSet.ItemIndex(0);
        for propName in pl do begin
            varProp := obj.Properties_.Item(propName,0);
            if not varIsNull(varProp) then
                propValue := varToStr(varProp)
            else propValue := '';
            valueList.Values[propName] := propValue;
        end;
    end;
    intVal :=  StrToInt(valueList.Values['AdapterRAM']);
    valueList.Values['AdapterRAM'] := intToStr(intVal shr 20);
    sl.Free;
    pl.Free;
end;

результат:
 

Метки:  wmi 

Комментарии

Игорь
28.04.2010 в 15:05

obj := objectSet.ItemIndex(0);
А в этой строчке нет ошибки? Ибо для объекта ObjectSet среда дает только метод Item :(

ter
28.04.2010 в 20:36
Согласно MSDN такого метода действительно нет. Но, видимо, данное описание соответствует библиотеке версии WMI Scripting v.1.1. В данном же примере импортирована библиотека версии 1.2 (Windows 7).
Тем не менее можно изменить код следующим образом:
var varEnum : IEnumVariant;
   varObj : oleVariant;
   fetched : cardinal;
...
   varEnum := objectSet._NewEnum as IEnumVariant;
   varEnum.Next(1,varObj,fetched);
   obj := IUnknown(varObj) as ISWbemObject;  
Игорь
29.04.2010 в 15:03
Спасибо за ответ. Однако до этого кода дело не дошло :), облом подкрался незаметно на запросе
objectSet :=services.ExecQuery(...


"Не верный запрос"

Будем поискать проблему

P.S. Я тоже импортировал библиотеку 1.2 (Win XP SP3), Delphi 2007
ter
29.04.2010 в 20:46
попробуйте напрямую вписать
select Caption, Version, OSArchitecture, WindowsDirectory from Win32_OperatingSystem

или сверить равно ли этому значение
'select '+ selectList + ' from ' + wmiClass
которое передается в ExecQuery
быть может где то пробел пропущен? в 'select ' Или 'from ' например.
Игорь
01.05.2010 в 14:36
В результате трасировки и 1 эксперимента выявил в ЧЕМ были проблемы в запросе. Аказывается во-первых в аргументе SELECT в принцепе не проходило перечисление, исключительно SELECT * FROM ...
И еще, в классе Win32_OperatingSystem отсутствует (у меня на компе) свойство OSArchitecture. А так все заработало.
Игорь
01.05.2010 в 14:31
Не на компе канешь, а в ОС (WinXP) :)
Виктор
21.08.2011 в 22:07
Все то хорошо, но мало и коротко. Вот как мне получить список запущенных процессов на удаленном компе, определенный по имени завершить, а другой процесс запустить? Как это воплотить на Делфиях?
Плиз, помогите.
ter
25.08.2011 в 16:38
с удаленными компами не пробовал такое, по идее для этих целей надо при коннекте указать комп/пользователь/пароль. WMI должен быть разрешен фаерволлом, возможно еще чем то.
дабы получить список активных процессов на компе (с удаленным работа только в части коннекта должна отличаться) код будет примерно таков (здесь plw : TListView)
procedure TForm3.FormCreate(Sender: TObject);
var     locator : ISWbemLocator;
    objSet : ISWbemObjectSet;
    obj : ISWbemObject;
    query : string;
    i : integer;
    li : TListItem;
    properties : ISWbemPropertySet;
    p : ISWbemProperty;
begin

    locator := CoSWbemLocator.Create();
    locator.Security_.Privileges.AddAsString('sedebugprivilege', true);
    services := locator.ConnectServer('.',
                                      'root\cimv2',
                                      '', '',
                                      '', '',
                                      0,
                                      nil);
    query := 'select Caption, CommandLine, handle from Win32_Process';
    objset := services.ExecQuery(query, 'WQL', wbemFlagReturnImmediately and wbemFlagForwardOnly, nil);

    for i := 0 to objset.Count - 1 do begin
        obj := objset.ItemIndex(i);
        properties := obj.Properties_;

        li := plw.Items.Add();
        li.Caption := intToStr(i);

        try
            li.SubItems.add( properties.Item('Caption',0).Get_Value);
            li.SubItems.add( properties.Item('CommandLine',0).Get_Value);
            li.SubItems.add( properties.Item('Handle',0).Get_Value);
        except

        end;

    end;


чтобы закрыть процесс, например блокнот, то надо его найти среди запущенных и вызвать метод terminate. тоже выглядит примерно так (коннект остается из предыдущего пункта):
procedure TForm3.Button1Click(Sender: TObject);
var query : string;
    objset : ISWbemObjectSet;
    obj : ISWbemObject;
    m : ISWbemMethod;
    inparams : ISWbemObject;
    prop : ISWbemProperty;
    propValue : OleVariant;
begin
    query := 'select Caption, CommandLine, handle from Win32_Process where Caption = ''notepad.exe''' ;
    objset := services.ExecQuery(query, 'WQL', wbemFlagReturnImmediately and wbemFlagForwardOnly, nil);

    if objset.Count = 0 then exit;

    obj := objset.ItemIndex(0);
    m := obj.Methods_.Item('Terminate',0);
    inparams := m.InParameters.SpawnInstance_(0);
    prop := inparams.Properties_.Add('reason', wbemCimtypeUint32, false, 0);
    propValue := 0;
    prop.Set_Value(propvalue);

    obj.ExecMethod_('Terminate', inparams, 0 ,nil);
end;


а запустить новый процесс можно так:
procedure TForm3.Button2Click(Sender: TObject);
var proc : ISWbemObject;
    suObj : ISWbemObject;
    m : ISWbemMethod;
    inparams : ISWbemObject;
    prop : ISWbemProperty;
    propval : OleVariant;
begin
    proc := services.Get('Win32_Process', 0, nil);
    m := proc.Methods_.Item('Create',0);
    inparams := m.InParameters.SpawnInstance_(0);

    prop := inparams.Properties_.Add('CommandLine',wbemCimtypeString, false, 0);
    propval := 'notepad.exe';
    prop.Set_Value(propVal);

    proc.ExecMethod_('Create',inparams,0,nil);

end;
но тут надо понимать от какого пользователя процесс будет запускаться.
Дмитрий
19.10.2011 в 02:50
Выдаёт ошибку на for propName in pl do begin - operator not applicable to this operand type
ter
19.10.2011 в 02:55
какую версию Delphi используете? до 2010й?
попробуйте замените цикл for-in на обычный for.
for j := 0 to pl.count -1 do begin
   propName := pl[j];
   ....
Касарь
11.01.2014 в 14:06
Мне понравился пример!
Но я пробую выводить список расширенных папок ('Win32_Share','Caption, Name, Path'), но он мне выводит только одну папку. Как решить этот вопрос?

Видимо где то перед перебором значения и перебором названия нужно сделать ещё один перебор на количество.

Можете помочь в этом вопросе? Delphi 7/Win 7.
Касарь
11.01.2014 в 15:29
Сам спросил.Сам отвечаю:
procedure  GetWMI;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('.', 'root\CIMV2', '', '');
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_Share','WQL',$00000020);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Form1.memo2.lines.add(Format('Caption        %s',[String(FWbemObject.Caption)]));// String
    Form1.memo2.lines.add(Format('Description    %s',[String(FWbemObject.Description)]));// String
    Form1.memo2.lines.add(Format('Name           %s',[String(FWbemObject.Name)]));// String
    Form1.memo2.lines.add(Format('Path           %s',[String(FWbemObject.Path)]));// String

    Form1.memo2.lines.add('***');
    FWbemObject:=Unassigned;
  end;
end;


procedure TForm1.Button9Click(Sender: TObject);
begin
 try
    CoInitialize(nil);
    try
      GetWMI;
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Form1.memo1.lines.add(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
        Form1.memo1.lines.add(E.Classname, ':', E.Message);
 end;

 end;
Пес
17.02.2014 в 18:09
я так и не понял как исправить ошибку
obj := objectSet.ItemIndex(0);
помогите
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно