Link to home
Start Free TrialLog in
Avatar of Wim ten Brink
Wim ten BrinkFlag for Netherlands

asked on

[Challenge] WMI translation...

I have imported the type library C:\WINNT\System32\wbem\wbemdisp.TLB into my project. (Without component wrappers!)

Now I also have this small VBScript example that tells me how to start an executable through WMI:
---------------------------------------------------------
set services = GetObject("winmgmts:root\cimv2")
Set obj = Services.Get("Win32_Process")
Set objIns = obj.Methods_("Create").InParameters.SpawnInstance_
objIns.CommandLine = "calc.exe"
Set objOut = Services.ExecMethod("Win32_Process", "Create", objIns)
MsgBox "Return value = " & objOut.returnvalue & VBCRLF & "Process ID = " & objout.processid
---------------------------------------------------------

Now, the challenge is not to just execute the application. Neither is it a challenge to just run this script in Delphi. No, the challenge is to convert the VBScript commands into Delphi commands with associated objects and whatever else. The purpose is not starting an external application or running a VBScript. No, I want to see how this code translates to WMI and I don't have too much time to do my own research at this moment. ;-)

Part of it is simple. The GetObject method can be replaced by:
function ADsGetObject(lpszPathName: WideString; const riid: TGUID; out ppObject): HRESULT; safecall; external 'activeds.dll';

So getting the services object is done like this:

var
  Services: SWbemServices;
begin
  if Succeeded(ADsGetObject('winmgmts:root\cimv2'), SWbemServices, Services) then begin
  end;
end;

Then, using the Services object I could create the other objects just like the VBScript does. However, I have problems translating the part where the 'Create' method is called to call the application and the execution of this command.
----------------------------------------------------
This is not an easy question, thus the high amount of points. Be warned: I am researching this myself too so if no one else provides me an example I will solve it myself.

I don't want hints. I don't want to know that applications can be executed in different ways. I just need a working example of above VBScript code translated to Delphi. This is meant to learn, not to solve an existing problem.
Avatar of Russell Libby
Russell Libby
Flag of United States of America image


Thought I would throw my 2 cents in on this one...

First, don't give VB script too much credit. The code it uses to get the services interface DOES NOT map to ADsGetObject (it does not have a CLUE about ADsGetObject). If your looking for a true equivalent to the GetObject(), then the following is pretty close:

function GetObject(Value: String): OleVariant;
var  pUnk:       IUnknown;
     pDisp:      IDispatch;
     pmk:        IMoniker;
     pbc:        IBindCtx;
     cbEaten:    LongInt;
     clsid:      TGUID;
begin

  // Resource protection
  try
     // Attempt to convert the string using the registry
     clsid:=ProgIDToClassID(Value);
     // Check running object table
     if (GetActiveObject(clsid, nil, pUnk) = S_OK) then
     begin
        // Return result as IDispatch
        result:=pUnk as IDispatch;
        // Release the refcount that we are holding on to
        pUnk:=nil;
     end
     else
        // Failed
        result:=Unassigned;
  except
     // Try to access the object using a moniker
     if (CreateBindCtx(0, pbc) = S_OK) then
     begin
        if (MkParseDisplayName(pbc, StringToOleStr(Value), cbEaten, pmk) = S_OK) then
        begin
           // Attempt to bind the moniker
           if (BindMoniker(pmk, 0, IDispatch, pDisp) = S_OK) then
              // Return the IDispatch
              result:=pDisp
           else
              // Return unassigned
              result:=Unassigned;
           // Release refcounts
           pDisp:=nil;
           pmk:=nil;
        end
        else
           // Return unassigned
           result:=Unassigned;
        // Release refcounts
        pbc:=nil;
     end
     else
        // Return unassigned
        result:=Unassigned;
  end;

end;

You will notice that the exception does get tripped (meaning the user did not pass a ProgID, but instead a moniker representation). But from there, the code goes through and binds up to the interface we are looking for.

The only other item of note is VB's allowance of shortcuts (which i hate because the code is no longer self documenting). The sample you displayed made use of this in accessing the methods collection. Delphi will require the .Item() syntax to work properly.

Then, using the above info, it becomes as simple as:

var  services:      OleVariant;
     obj:           OleVariant;
     objIns:        OleVariant;
     objOut:        OleVariant;
begin

  services:=GetObject('winmgmts:root\cimv2');
  obj:=Services.Get('Win32_Process');
  objIns:=obj.Methods_.Item('Create').InParameters.SpawnInstance_;
  objIns.CommandLine:='calc.exe';
  objOut:=Services.ExecMethod('Win32_Process', 'Create', objIns);
  ShowMessage('Return value = '+IntToStr(objOut.returnvalue)+#13#10+'Process ID = '+IntToStr(objout.processid));

end;

Hope this helps,
Russell


Avatar of Wim ten Brink

ASKER

Looks good so far. Wished I could avoid the OleVariants in my code, though. Can't use Code Completion with OleVariants... :-)

And yes, you noticed what I noticed when I first tried to translate it. The VB shortcut for Item. Can't test it right now, though. But will check in about 12 hours when I'm back at work again.

The part about binding the moniker (CreateBindCtx & BindMoniker) are quite helpful, btw. But if I'm correct, you can specify the interface type with BindMoniker thus an OleVariant doesn't have to be required. I know VBScript only uses untyped variables but I prefer to avoid them as much as possible.

It looks good for now, though. :-)
ASKER CERTIFIED SOLUTION
Avatar of Russell Libby
Russell Libby
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Lee_Nover
Lee_Nover

try WMISet 1.5 - http://www.online-admin.com/wmiset.html
I remember seeing some other wmi delphi implementations .. I'll post if I remember/firnd them
Okay, so far I've gotten to this, thanks to rllibby:

function MonikerObject(Moniker: string; const riid: TGUID; out ppObject): HRESULT;
var
  pmk: IMoniker;
  pbc: IBindCtx;
  cbEaten: LongInt;
begin
  IUnknown(ppObject) := nil;
  Result := CreateBindCtx(0, pbc);
  if (Result = S_OK) then Result := MkParseDisplayName(pbc, StringToOleStr(Moniker), cbEaten, pmk);
  if (Result = S_OK) then Result := BindMoniker(pmk, 0, IDispatch, ppObject);
  if not (Result = S_OK) then IUnknown(ppObject) := nil;
  pmk := nil;
  pbc := nil;
end;

I created this function to create an object through the moniker. With WMI I will be mostly using monikers anyway so this code cleaned it up a bit. It also allows me make it more type-specific, like the ADsGetObject function.
However, the second part still has a flaw:

var
  Services: SWbemServices;
  Obj: SWbemObject;
  ObjIns: SWbemObject;
  ObjOut: SWbemObject;
  varValue: OleVariant;
begin
  if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED or COINIT_DISABLE_OLE1DDE or COINIT_SPEED_OVER_MEMORY)) then begin
    if Succeeded(MonikerObject('winmgmts:root\cimv2', SWbemServices, Services)) then begin
      Obj := Services.Get('Win32_Process', 0, nil);
      objIns := obj.Methods_.Item('Create', 0).InParameters.SpawnInstance_(0);
      Obj := nil;
      // objIns.CommandLine := 'calc.exe'; // Fails since CommandLine is unknown.
      varValue := 'calc.exe';
      objIns.Properties_.Item('CommandLine', 0).Set_Value(varValue);
      objOut := Services.ExecMethod('Win32_Process', 'Create', objIns, 0, nil);
      objIns := nil;
      WriteLn('Return value = ', objOut.Properties_.Item('ReturnValue', 0).Get_Value);
      WriteLn('Process ID = ', objout.Properties_.Item('ProcessID', 0).Get_Value);
      objOut := nil;
    end;
    CoUninitialize;
  except
    on E: Exception do begin
      WriteLn(E.Message);
      CoUninitialize;
    end;
  end;
end.

Yeah, setting the CommandLine property is the real pain here. I still need an OleVariant to pass a string to this SetValue function... Typecasting doesn't work since the Set_Value procedure declares the parameter as "var varValue: OleVariant"...

Oh, well... I can live with this... :-)
--------------------------------------------------------------

Lee_Nover: I knew the link already. It's an interesting set of components for only $49 but I want to learn to do it myself, not depend on someone's else's components. :-) Another useful link is http://www.iisfaq.com/ExternalLink.aspx?L=1561&P=125 which is where I found my first working WMI code for Delphi. :-) It's called "A better Windows Management Instrumentation (WMI) Demo." and is somewhere in the middle of the page. The biggest problem I had was getting the object a bit more correctly which I can now do. (Goodbye ADsGetObject!) rllibby was a great help at that.
And you're gaining on me again, rllibby... :-)
nice :)