14

I wrote a simple function to retrieve system information using the WMI, passing as parameter the class and the property name. when I execute the function like this

  Writeln('Procesor Id '+GetWMIInfo('Win32_Processor','Name'));
  Writeln('Mother Board Serial '+GetWMIInfo('Win32_BaseBoard','SerialNumber'));
  Writeln('BIOS Version '+GetWMIInfo('Win32_BIOS','Version'));

The execution time is about 1300 ms.

I need retrieve a lot of additional information, So Is possible reduce the time of execution of this function?

This is a sample application with the function

{$APPTYPE CONSOLE}

uses
  Diagnostics,
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

function  GetWMIInfo(const WMIClass, WMIProperty:string): string;
var
  sWbemLocator  : OLEVariant;
  sWMIService   : OLEVariant;
  sWbemObjectSet: OLEVariant;
  sWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  Result:='';
  sWbemLocator  := CreateOleObject('WbemScripting.SWbemLocator');
  sWMIService   := sWbemLocator.ConnectServer('', 'root\CIMV2', '', '');
  sWbemObjectSet:= sWMIService.ExecQuery('SELECT * FROM '+WMIClass,'WQL');
  oEnum         := IUnknown(sWbemObjectSet._NewEnum) as IEnumVariant;
  if oEnum.Next(1, sWbemObject, iValue) = 0 then
    Result:=sWbemObject.Properties_.Item(WMIProperty).Value;
end;

var
 SW : TStopwatch;

begin
 try
    CoInitialize(nil);
    try
      SW.Reset;
      SW.Start;
      Writeln('Procesor Id '+GetWMIInfo('Win32_Processor','Name'));
      Writeln('Mother Board Serial '+GetWMIInfo('Win32_BaseBoard','SerialNumber'));
      Writeln('BIOS Version '+GetWMIInfo('Win32_BIOS','Version'));
      SW.Stop;
      Writeln('Elapsed ms '+FormatFloat('#,0.000',SW.Elapsed.TotalMilliseconds));
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.
Salvador
  • 16,132
  • 33
  • 143
  • 245

2 Answers2

21

These are some tips to improve the WMI performance

1.) Reutilize the call to CreateOleObject

2.) Reuse the WMI Connection

One of more expensive tasks is make a connection to the WMI services, so reutilize that conneciton instead of create one conneciton each time which call the function.

3.) Only retrieve the columns which you want to use

Every property which retrieve the WMI has different sources like the Windows registry, the WinAPi and so on, restricting the columns will improve the performance. read this article for more info How obtain the source of the WMI Data

4.) Use the WBEM_FLAG_FORWARD_ONLY flag when you execute the WQL sentence.

Following the above tips I rewrote your sample app

{$APPTYPE CONSOLE}

uses
  Diagnostics,
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;

function  GetWMIInfo(const WMIClass, WMIProperty:string): string;
const
  wbemFlagForwardOnly = $00000020;
var
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  Result:='';
  FWbemObjectSet:= FWMIService.ExecQuery(Format('Select %s from %s',[WMIProperty, WMIClass]),'WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  if oEnum.Next(1, FWbemObject, iValue) = 0 then
    Result:=FWbemObject.Properties_.Item(WMIProperty).Value;
end;

var
 SW : TStopwatch;

begin
 try
    CoInitialize(nil);
    try
      SW.Reset;
      SW.Start;
      FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
      FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
      Writeln('Procesor Id '+GetWMIInfo('Win32_Processor','Name'));
      Writeln('Mother Board Serial '+GetWMIInfo('Win32_BaseBoard','SerialNumber'));
      Writeln('BIOS Version '+GetWMIInfo('Win32_BIOS','Version'));
      SW.Stop;
      Writeln('Elapsed ms '+FormatFloat('#,0.000',SW.Elapsed.TotalMilliseconds));
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

And the execution goes from 1245 to 180 ms (on my laptop).

Community
  • 1
  • 1
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • 6
    +1. Nice, and again you make me delete my inferior answer. :) In addition to what you said, the poster might want to look at MagWMI, a free set of Delphi wrappers that make things easier. It internally does some caching of information that makes multiple queries much easier. The wrapper is free, and comes with a really extensive demo app that might help. – Ken White Apr 17 '12 at 22:03
  • 2
    yep, this is pretty much the same thing that I was saying...however personally I think a better general pattern is to pass in the cached service vars to the function, this allows for DI and mocking of those vars in a testing framework. – Tim Jarvis Apr 17 '12 at 22:17
  • Off course better is create a object to encapsulate the connection and the function. this sample app is just a proof of concept. Also the point 3 never is mentioned and is very important. – RRUZ Apr 17 '12 at 22:22
  • Can the above function be modified so that it can retrieve multiple parameters into a single call? Eg: GetWMIInfo('Win32_LogicalDisk','Caption,FreeSpace,Size'). It would be nice if it could retrieve all information regarding all existing partitions like the cmd counterpart does: wmic logicaldisk where mediatype=12 get caption,size,freespace /format:LIST – user2858981 Aug 17 '17 at 15:41
4

This is a general rule of thumb.

When you say you want to retrieve a lot of additional information I assume that means you will be calling this function a lot, possibly in a loop. For performance tuning, you simply need to take those things that cost a lot of time and that you can reuse, out of the loop i.e. cache them.

In this case the CreateOleObject is likely to be costing you the bulk of the time, as a first pass I would put that outside the loop (or multiple calls) and pass your sWebLocator into the function, as a second pass you might like to take the ConnectServer call out of the function as well and pass the sWMIService object in as well.

Tim Jarvis
  • 18,465
  • 9
  • 55
  • 92