The Road to Delphi

Delphi – Free Pascal – Oxygene


14 Comments

Manipulating local/remote files and folders using Delphi and WMI

Some days ago a question in stackoverflow.com was made, about how list the contents of a folder of a remote machine. I answer that question using a WMI approach mentioning the CIM_DataFile and CIM_Directory WMI Classes, these classes  can be used to retrieve information about files, folders and perform many task like copy, rename, delete and  compress.  So today I will show you how you can use these classes form Delphi.

First you must know which in order to access any file or folder  in a remote machine need not be a shared resource, only just must enable the WMI remote access in the machine to access, check these articles for more details about how enable the remote wmi access.

After of enabling the WMI remote access in the client machine you are ready to access the files and folders.

Before to begin

Now some tips which can help you to deal both classes

  1. When you make a WMI query against these classes always you must use filters (Where conditions) to restrict the result of these WMI classes.
  2. Always you must use the Drive field as condition, due to two reasons, first these classes will scan all directories on any available storage device. So this task can take some time. and second to diffentiate for example a folder called Windows located in the Drive C: and in the Drive D:.
  3. The Wmi interprets the \ (Backslash) character as a reserved symbol so you need to escape that character to avoid problems with the WQL sentence.
  4. You can use these classes to find files or folders which match with a criteria, but remember if you not specify the Path property the WMI will scan the entire drive. so try to avoid  sentences like
FWMIService.ExecQuery(Format('SELECT * FROM CIM_DataFile Where Drive="%s" AND Extension="%s"',['C:','jpg']),'WQL',wbemFlagForwardOnly);//this will return all the jpg files in the C Drive

Note :
The code showed in this article uses Late binding to access the WMI, if you want use another way to access the WMI from delphi (like direct COM access or importing th e Microsoft scripting library) take a look to the Delphi WMI Code creator.

Listing Folder and Files

//list the files and folders of a specified Path (non recursive)
procedure  ListFolderContent(Const WbemComputer,WbemUser,WbemPassword,Path:string);
const
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
  WmiPath       : string;
  Drive         : string;
begin;
  //Extract the drive from the Path
  Drive   :=ExtractFileDrive(Path);
  //add a back slash to the end of the folder
  WmiPath :=IncludeTrailingPathDelimiter(Copy(Path,3,Length(Path)));
  //escape the folder name
  WmiPath :=StringReplace(WmiPath,'\','\\',[rfReplaceAll]);

  Writeln('Connecting');
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  //establish the connection
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);

  Writeln('Files');
  //get the files
  FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM CIM_DataFile Where Drive="%s" AND Path="%s"',[Drive,WmiPath]),'WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('%s',[FWbemObject.Name]));// String
    FWbemObject:=Unassigned;
  end;

  Writeln('Folders');
  //get the folders
  FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM CIM_Directory Where Drive="%s" AND Path="%s"',[Drive,WmiPath]),'WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('%s',[FWbemObject.Name]));// String
    FWbemObject:=Unassigned;
  end;
end;

How to use

begin
 try
    CoInitialize(nil);
    try
      //ListFolderContent('.','','','C:\data'); //get the content of the folder Data in he local machine
      ListFolderContent('remotemachine','user_name','password','C:\Data');
    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;
 Writeln('Press Enter to exit');
 Readln;
end.

Get info about a particular file or folder


This code will list the type, size, creation date, attributes, etc. of any file which you pass as parameter

procedure  GetCIM_DataFileInfo(const FileName:string);
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
  Writeln(Format('AccessMask               %s',[FWbemObject.AccessMask]));// Uint32
  Writeln(Format('Archive                  %s',[FWbemObject.Archive]));// Boolean
  Writeln(Format('Caption                  %s',[FWbemObject.Caption]));// String
  Writeln(Format('Compressed               %s',[FWbemObject.Compressed]));// Boolean
  Writeln(Format('CompressionMethod        %s',[FWbemObject.CompressionMethod]));// String
  Writeln(Format('CreationClassName        %s',[FWbemObject.CreationClassName]));// String
  Writeln(Format('CreationDate             %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.CreationDate))]));// Datetime
  Writeln(Format('CSCreationClassName      %s',[FWbemObject.CSCreationClassName]));// String
  Writeln(Format('CSName                   %s',[FWbemObject.CSName]));// String
  Writeln(Format('Description              %s',[FWbemObject.Description]));// String
  Writeln(Format('Drive                    %s',[FWbemObject.Drive]));// String
  Writeln(Format('EightDotThreeFileName    %s',[FWbemObject.EightDotThreeFileName]));// String
  Writeln(Format('Encrypted                %s',[FWbemObject.Encrypted]));// Boolean
  Writeln(Format('EncryptionMethod         %s',[FWbemObject.EncryptionMethod]));// String
  Writeln(Format('Extension                %s',[FWbemObject.Extension]));// String
  Writeln(Format('FileName                 %s',[FWbemObject.FileName]));// String
  Writeln(Format('FileSize                 %s',[FWbemObject.FileSize]));// Uint64
  Writeln(Format('FileType                 %s',[FWbemObject.FileType]));// String
  Writeln(Format('FSCreationClassName      %s',[FWbemObject.FSCreationClassName]));// String
  Writeln(Format('FSName                   %s',[FWbemObject.FSName]));// String
  Writeln(Format('Hidden                   %s',[FWbemObject.Hidden]));// Boolean
  Writeln(Format('InstallDate              %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.InstallDate))]));// Datetime
  Writeln(Format('InUseCount               %s',[FWbemObject.InUseCount]));// Uint64
  Writeln(Format('LastAccessed             %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.LastAccessed))]));// Datetime
  Writeln(Format('LastModified             %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.LastModified))]));// Datetime
  Writeln(Format('Manufacturer             %s',[FWbemObject.Manufacturer]));// String
  Writeln(Format('Name                     %s',[FWbemObject.Name]));// String
  Writeln(Format('Path                     %s',[FWbemObject.Path]));// String
  Writeln(Format('Readable                 %s',[FWbemObject.Readable]));// Boolean
  Writeln(Format('Status                   %s',[FWbemObject.Status]));// String
  Writeln(Format('System                   %s',[FWbemObject.System]));// Boolean
  Writeln(Format('Version                  %s',[FWbemObject.Version]));// String
  Writeln(Format('Writeable                %s',[FWbemObject.Writeable]));// Boolean
  Writeln('');
end;

In the above code you note two things, first I’m using the SWbemServices.Get method instead SWbemServices.ExecQuery this is because the Get function retrieve a single instance of the wmi object path passed as parameter  (the wmi object path is a string that uniquely identifies a instances of a class). The second
which you must note is the WbemTimeToDateTime  function, this is a helper function to convert the returned WMI datetime values in UTC format to TDateTime.

This is the code of that function, for more info check the documentation about the WbemScripting.SWbemDateTime object.

function WbemTimeToDateTime(const V : OleVariant): TDateTime;
var
  Dt : OleVariant;
begin
  Result:=0;
  if VarIsNull(V) then exit;
  Dt:=CreateOleObject('WbemScripting.SWbemDateTime');
  Dt.Value := V;
  Result:=Dt.GetVarDate;
end;

This code will list the size, creation date, atributes, etc. of any folder which you pass as parameter

procedure  GetCIM_DirectoryInfo(const FolderName:string);
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(FolderName,'\','\\',[rfReplaceAll])]));
  Writeln(Format('AccessMask               %s',[FWbemObject.AccessMask]));// Uint32
  Writeln(Format('Archive                  %s',[FWbemObject.Archive]));// Boolean
  Writeln(Format('Caption                  %s',[FWbemObject.Caption]));// String
  Writeln(Format('Compressed               %s',[FWbemObject.Compressed]));// Boolean
  Writeln(Format('CompressionMethod        %s',[FWbemObject.CompressionMethod]));// String
  Writeln(Format('CreationClassName        %s',[FWbemObject.CreationClassName]));// String
  Writeln(Format('CreationDate             %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.CreationDate))]));// Datetime
  Writeln(Format('CSCreationClassName      %s',[FWbemObject.CSCreationClassName]));// String
  Writeln(Format('CSName                   %s',[FWbemObject.CSName]));// String
  Writeln(Format('Description              %s',[FWbemObject.Description]));// String
  Writeln(Format('Drive                    %s',[FWbemObject.Drive]));// String
  Writeln(Format('EightDotThreeFileName    %s',[FWbemObject.EightDotThreeFileName]));// String
  Writeln(Format('Encrypted                %s',[FWbemObject.Encrypted]));// Boolean
  Writeln(Format('EncryptionMethod         %s',[FWbemObject.EncryptionMethod]));// String
  Writeln(Format('Extension                %s',[FWbemObject.Extension]));// String
  Writeln(Format('FileName                 %s',[FWbemObject.FileName]));// String
  Writeln(Format('FileSize                 %s',[FWbemObject.FileSize]));// Uint64
  Writeln(Format('FileType                 %s',[FWbemObject.FileType]));// String
  Writeln(Format('FSCreationClassName      %s',[FWbemObject.FSCreationClassName]));// String
  Writeln(Format('FSName                   %s',[FWbemObject.FSName]));// String
  Writeln(Format('Hidden                   %s',[FWbemObject.Hidden]));// Boolean
  Writeln(Format('InstallDate              %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.InstallDate))]));// Datetime
  Writeln(Format('InUseCount               %s',[FWbemObject.InUseCount]));// Uint64
  Writeln(Format('LastAccessed             %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.LastAccessed))]));// Datetime
  Writeln(Format('LastModified             %s',[FormatDateTime('dd/mm/yyyy hh:nn:ss',WbemTimeToDateTime(FWbemObject.LastModified))]));// Datetime
  Writeln(Format('Name                     %s',[FWbemObject.Name]));// String
  Writeln(Format('Path                     %s',[FWbemObject.Path]));// String
  Writeln(Format('Readable                 %s',[FWbemObject.Readable]));// Boolean
  Writeln(Format('Status                   %s',[FWbemObject.Status]));// String
  Writeln(Format('System                   %s',[FWbemObject.System]));// Boolean
  Writeln(Format('Writeable                %s',[FWbemObject.Writeable]));// Boolean
  Writeln('');
end;

Compress a File or Folder using the NTFS compression

function  CompressFile(const FileName:string):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.Compress();
end;
function  CompressFolder(const FolderName:string;Recursive:Boolean):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
  StopFileName  : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(FolderName,'\','\\',[rfReplaceAll])]));
  if Recursive then
   Result:=FWbemObject.CompressEx(StopFileName, Null, Recursive)
  else
   Result:=FWbemObject.Compress();
end;

UnCompress a File or Folder using the NTFS compression

function  UnCompressFile(const FileName:string):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.UnCompress();
end;
function  UnCompressFolder(const FolderName:string;Recursive:Boolean):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
  StopFileName  : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(FolderName,'\','\\',[rfReplaceAll])]));
  if Recursive then
   Result:=FWbemObject.UnCompressEx(StopFileName, Variants.Null, Recursive)
  else
   Result:=FWbemObject.UnCompress();
end;

Copy a single File or Folder

function  CopyFile(const SourceFileName,DestFileName:string):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(SourceFileName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.Copy(DestFileName);
end;
function  CopyFolder(const SourceFolder,DestFolder:string;Recursive:Boolean):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
  StopFileName  : OleVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(SourceFolder,'\','\\',[rfReplaceAll])]));
  if Recursive then
    Result:=FWbemObject.CopyEx(DestFolder,StopFileName, Variants.Null, Recursive)
  else
    Result:=FWbemObject.Copy(DestFolder);
end;

Delete a File or Folder

function  DeleteFile(const FileName:string):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.Delete();
end;
//remove all the contents of the folder recursivily
function  DeleteFolder(const FolderName:string):integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(FolderName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.Delete();
end;

Rename a File or Folder

function RenameFile(const OldName, NewName: string): Integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(OldName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.Rename(NewName);
end;
function RenameFolder(const OldName, NewName: string): Integer;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_Directory.Name="%s"',[StringReplace(OldName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.Rename(NewName);
end;

Getting the permissions over a CIM_DataFile or CIM_Directory object

In order to get whether the caller has the permissions on the CIM_DataFile or CIM_Directory object you must use the GetEffectivePermission function with these flags

Value (Dec/Hex) Meaning
FILE_READ_DATA (file)FILE_LIST_DIRECTORY (directory)
1 (0x1)
Grants the right to read data from the file. For a directory, this value grants the right to list the contents of the directory.
FILE_WRITE_DATA (file)FILE_ADD_FILE (directory)
2 (0x2)
Grants the right to write data to the file. For a directory, this value grants the right to create a file in the directory.
FILE_APPEND_DATA (file)FILE_ADD_SUBDIRECTORY (directory)
4 (0x4)
Grants the right to append data to the file. For a directory, this value grants the right to create a subdirectory.
FILE_READ_EA
8 (0x8)
Grants the right to read extended attributes.
FILE_WRITE_EA
16 (0x10)
Grants the right to write extended attributes.
FILE_EXECUTE (file)FILE_TRAVERSE (directory)
32 (0x20)
Grants the right to execute a file. For a directory, the directory can be traversed.
FILE_DELETE_CHILD (directory)
64 (0x40)
Grants the right to delete a directory and all the files it contains, even if the files are read-only.
FILE_READ_ATTRIBUTES
128 (0x80)
Grants the right to read file attributes.
FILE_WRITE_ATTRIBUTES
256 (0x100)
Grants the right to change file attributes.
DELETE
65536 (0x10000)
Grants delete access.
READ_CONTROL
131072 (0x20000)
Grants read access to the security descriptor and owner.
WRITE_DAC
262144 (0x40000)
Grants write access to the discretionary ACL.
WRITE_OWNER
524288 (0x80000)
Assigns the write owner.
SYNCHRONIZE
1048576 (0x100000)
Synchronizes access and allows a process to wait for an object to enter the signaled state.
function GetEffectivePermission(const FileName:string;Flags:integer):Boolean;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObject   : OLEVariant;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObject   := FWMIService.Get(Format('CIM_DataFile.Name="%s"',[StringReplace(FileName,'\','\\',[rfReplaceAll])]));
  Result:=FWbemObject.GetEffectivePermission(Flags);
end;

And use like this

GetEffectivePermission('C:\FooFolder\Foo_Filename.ext',FILE_READ_DATA or FILE_READ_ATTRIBUTES);

Getting the Status of a task

The functions Compress, UnCompress, Copy, Rename and Delete return a Result code which can be translated to a message using this function

function GetResultMessage(ResultCode:Integer) : string;
begin
  case ResultCode of
     0 : Result:='Success';
     2 : Result:='Access denied';
     8 : Result:='Unspecified failure';
     9 : Result:='Invalid object';
    10 : Result:='Object already exists';
    11 : Result:='File system not NTFS';
    12 : Result:='Platform not Windows NT or Windows 2000';
    13 : Result:='Drive not the same';
    14 : Result:='Directory not empty';
    15 : Result:='Sharing violation';
    16 : Result:='Invalid start file';
    17 : Result:='Privilege not held';
    21 : Result:='Invalid parameter'
  else
    Result := 'Unknown';
  end;
end;

Demo Application

Check this demo application which can list all the files and folders in a remote or local machine only using the WMI

checkout the full source code on Github.

 


5 Comments

Two ways to get the command line of another process using delphi

Note : for a updated version of the code check the Github repo.


Today I will show you how you can retrieve the Command line parameters of an external application from Delphi using the WinApi and the WMI. In order to understand how the Command line parameters are stored and treated by the system, I recommend which you read this article from  Raymond Chen .

The WinApi way

In order to get the command line from an external process using the WinAPI, you must access to the PEB (Process Environment Block) of the application. To get the PEB you can use the NtQueryInformationProcess function

NTSTATUS WINAPI NtQueryInformationProcess(
  __in       HANDLE ProcessHandle,
  __in       PROCESSINFOCLASS ProcessInformationClass,
  __out      PVOID ProcessInformation,
  __in       ULONG ProcessInformationLength,
  __out_opt  PULONG ReturnLength
);

 

function  NtQueryInformationProcess(
  ProcessHandle : THandle;
  ProcessInformationClass : DWORD;
  ProcessInformation : Pointer;
  ProcessInformationLength : ULONG;
  ReturnLength : PULONG
 ): LongInt; stdcall; external 'ntdll.dll';

Passing the ProcessBasicInformation value in the ProcessInformationClass parameter and a buffer to hold the PROCESS_BASIC_INFORMATION returned in the ProcessInformation.

This is the official (MSDN) definition for the PROCESS_BASIC_INFORMATION structure

typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

And this a more friendly delphi translation of this structure using the NTinterlnals.net site

  PROCESS_BASIC_INFORMATION = packed record
    ExitStatus: DWORD;
    PebBaseAddress: Pointer;
    AffinityMask: DWORD;
    BasePriority: DWORD;
    UniqueProcessId: DWORD;
    InheritedUniquePID:DWORD;
  end;

The key field in this structure is PebBaseAddress, which stores the address of the PEB. from this point now you must digging inside of the PEB structure again

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;

and retrieve the value of the ProcessParameters field which is a pointer to a RTL_USER_PROCESS_PARAMETERS structure

typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE           Reserved1[16];
  PVOID          Reserved2[10];
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

Finally you can note which the CommandLine field stores the info which are looking for.

The WinAPI Delphi Code

This is the Delphi source which retrieves the Command line parameters from an external application
Notes :

  1. the next code uses hard-coded offsets to read specific locations of the PEB to avoid the declaration the full structures required (feel free to declare these structures and avoid the offsets).
  2. this code only works for 32 bits process because the structure of the PEB differs from 32 to 64 processes.
  3. to gain access to the processes owned by the system the code set the  SeDebugPrivilege token before to use the OpenProcess function.
//Author Rodrigo Ruz V.
//2011-07-20
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Windows;

type
  _UNICODE_STRING = record
    Length: Word;
    MaximumLength: Word;
    Buffer: LPWSTR;
  end;
  UNICODE_STRING = _UNICODE_STRING;

  PROCESS_BASIC_INFORMATION = packed record
    ExitStatus: DWORD;
    PebBaseAddress: Pointer;
    AffinityMask: DWORD;
    BasePriority: DWORD;
    UniqueProcessId: DWORD;
    InheritedUniquePID:DWORD;
  end;

  function  NtQueryInformationProcess(ProcessHandle : THandle; ProcessInformationClass : DWORD; ProcessInformation : Pointer; ProcessInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll';

function GetCommandLineFromPid(PID: THandle): string;
const
  STATUS_SUCCESS             = $00000000;
  SE_DEBUG_NAME              = 'SeDebugPrivilege';
  OffsetProcessParametersx32 = $10;//16
  OffsetCommandLinex32       = $40;//64
var
  ProcessHandle        : THandle;
  rtlUserProcAddress   : Pointer;
  CommandLine          : UNICODE_STRING;
  CommandLineContents  : WideString;
  ProcessBasicInfo     : PROCESS_BASIC_INFORMATION;
  ReturnLength         : Cardinal;
  TokenHandle          : THandle;
  lpLuid               : TOKEN_PRIVILEGES;
  OldlpLuid            : TOKEN_PRIVILEGES;
begin
  Result:='';
  if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then
  begin
    try

      if not LookupPrivilegeValue(nil, SE_DEBUG_NAME, lpLuid.Privileges[0].Luid) then
        RaiseLastOSError
      else
      begin
        lpLuid.PrivilegeCount := 1;
        lpLuid.Privileges[0].Attributes  := SE_PRIVILEGE_ENABLED;
        ReturnLength := 0;
        OldlpLuid    := lpLuid;
        //Set the SeDebugPrivilege privilege
        if not AdjustTokenPrivileges(TokenHandle, False, lpLuid, SizeOf(OldlpLuid), OldlpLuid, ReturnLength) then RaiseLastOSError;
      end;

      ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, PID);
      if ProcessHandle=0 then RaiseLastOSError
      else
      try
        // get the PROCESS_BASIC_INFORMATION to access to the PEB Address
        if (NtQueryInformationProcess(ProcessHandle,0{=>ProcessBasicInformation},@ProcessBasicInfo, sizeof(ProcessBasicInfo), @ReturnLength)=STATUS_SUCCESS) and (ReturnLength=SizeOf(ProcessBasicInfo)) then
        begin
          //get the address of the RTL_USER_PROCESS_PARAMETERS struture
          if not ReadProcessMemory(ProcessHandle, Pointer(Longint(ProcessBasicInfo.PEBBaseAddress) + OffsetProcessParametersx32), @rtlUserProcAddress, sizeof(Pointer), ReturnLength) then
            RaiseLastOSError
          else
          if ReadProcessMemory(ProcessHandle, Pointer(Longint(rtlUserProcAddress) + OffsetCommandLinex32), @CommandLine, sizeof(CommandLine), ReturnLength) then
          begin
            SetLength(CommandLineContents, CommandLine.length);
            //get the CommandLine field
            if ReadProcessMemory(ProcessHandle, CommandLine.Buffer, @CommandLineContents[1], CommandLine.Length, ReturnLength) then
             Result := WideCharLenToString(PWideChar(CommandLineContents), CommandLine.length div 2)
            else
            RaiseLastOSError;
          end;
        end
        else
        RaiseLastOSError;
      finally
        CloseHandle(ProcessHandle);
      end;
    finally
      CloseHandle(TokenHandle);
    end;
  end
  else
  RaiseLastOSError;
end;

begin
 try
   Writeln(GetCommandLineFromPid(5440));
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

The WMI way

The WMI provides a very reliable and easy way to access the Command line parameters from an external process, all you must to do is use the Win32_Process wmi class and look in the CommandLine property.

The WMI Delphi Code

Notes

  1. The next code can retrieve the command line for 32 and 64 bits processes.
  2. The code uses Late binding to access the WMI, if you want use another way to access the WMI from Delphi (like direct COM access or importing th e Microsoft scripting library) take a look to the Delphi WMI Code creator.
  3. You can change the credentials of the ConnectServer function to access to the command line parameters of a remote machine process.
{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  ActiveX,
  Variants,
  ComObj;

function  GetCommandLineFromPid(ProcessId:DWORD): string;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
begin;
  Result:='';
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  //if the pid not exist a EOleException exception will be raised with the code $80041002 - Object Not Found
  FWbemObjectSet:= FWMIService.Get(Format('Win32_Process.Handle="%d"',[ProcessId]));
  Result:=FWbemObjectSet.CommandLine;
end;

begin
 try
    CoInitialize(nil);
    try
     Writeln(GetCommandLineFromPid(5452));
    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;
 Writeln('Press Enter to exit');
 Readln;
end.


2 Comments

WMI Delphi Code Creator – New features and source code published in google code

Update : The WMI Delphi Code Creator now is hosted on Github.

In the last months I’ been working in a new version of the WMI Delphi Code Creator, porting the original code from Delphi 2007 to XE, adding new features like support for Delphi Prism and Free Pascal, and improving the source code generated by the tool. Now is time to show the results, so the new version is here with many new features,  also  the full source code is now available in the google code project hosting site under the Mozilla Public License 1.1

 

Check the list of features of the current version

  • Can generate object pascal code compatible with one of these compilers Delphi Win32, Delphi -Prism (Oxygene) , Free Pascal
  • The Delphi code generated is compatible with  Delphi 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE.
  • The Free Pascal code generated is compatible with these versions 2.4.2, 2.4.4
  • The Delphi prism .Net  (Oxygene) generated code is compatible with all the versions up to 4.0.23.741 (in newer versions must work too)
  • Full access to metadata of any WMI Class registered in the system including qualifiers, mof definition, properties, methods, events
  • You can access directly from the application the MSDN web page related to the WMI Class which your are using.
  • Compile and run the generated code directly form the application using the selected compiler.
  • Open the the generated Delphi code in any of these  Delphi IDE’s 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE
  • Open the the generated Free Pascal code directly in the Lazarus IDE.
  • Open the the generated Delphi Prism code directly in Visual Studio 2008, Visual Studio 2010 or MonoDevelop.
  • Runs on Windows XP, 2003, 2008, Vista and 7.
  • Themes support for Syntax highlighting (+50 themes included) compatible with the Delphi IDE Theme Editor.

For download the new version of the application and more details go to project page


16 Comments

Accesing the WMI from Delphi and Free Pascal via COM (without late binding or WbemScripting_TLB)

A fellow Delphi programmer,  ask me how they can access the WMI using the  COM API for WMI ,  so I decide write this article to show how.

First you must to know which this API was designed primarily for low level access to the WMI from C++ and for create WMI providers, compile mof files and so on.

In the past articles always I show samples to use the WMI using late binding or importing the Microsoft WMIScripting Library. in both cases you are using the same layer to access the WMI (WMIScripting).

In the next diagram you can see the layers to access the WMI, you can note how the WMIScripting finally access the WMI using the WMI COM API. In the next sample you will learn how avoid this additional layer.

The interfaces of the COM API for WMI are very similar to the Microsoft WMIScripting Library because the last is just a wrapper for the COM object.

Note : the code showed in this article was tested in Delphi 2007, Delphi XE and FPC 2.4.2 and uses the WBEM Client interface Unit for Object Pascal which is an translation of the headers of the WbemCli.h file. this unit called JwaWbemCli is part of the JEDI API Library

Accessing the WMI using the COM Interface


Initialize COM

Microsoft recommends use the CoInitializeEx function with the COINIT_MULTITHREADED flag
the code will looks like so

  if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED)) then
  try
    //Execute your WMI code here
  finally
    CoUninitialize();
  end;

Set the general COM security level

Now In order to set the general COM security level you must perform a call to the CoInitializeSecurity function.

CoInitializeSecurity(nil, -1, nil, nil, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nil, EOAC_NONE, nil);

Create a connection to a WMI namespace.

FWbemLocator.ConnectServer(strNetworkResource, strUser, strPassword, strLocale,  WBEM_FLAG_CONNECT_USE_MAX_WAIT, strAuthority, nil, FWbemServices)

Set the security levels on the WMI connection.

By definition, WMI runs in a different process than your application. Therefore, you must create a connection between your application and WMI and you must set the impersonation and authentication levels for your application. this must be done using the CoSetProxyBlanket and CoCreateInstance functions.

 CoSetProxyBlanket(FWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nil, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nil, EOAC_NONE);
 CoCreateInstance(CLSID_UnsecuredApartment, nil, CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, FUnsecuredApartment);

Implement your application (make the WMI query)

        Succeed := FWbemServices.ExecQuery('WQL', WQL, WBEM_FLAG_FORWARD_ONLY, nil, ppEnum);
        if Succeeded(Succeed) then
        begin
           // Get the data from the query
           while (ppEnum.Next(WBEM_INFINITE, 1, apObjects, puReturned)=0) do
           begin
             apObjects.Get('Caption', 0, pVal, pType, plFlavor);
             Writeln(pVal);
             VarClear(pVal);
           end;
        end
        else
        Writeln(Format('Error executing WQL sentence %x',[Succeed]));

Finally Cleanup and shut down your application.

After you complete your queries to WMI, you should destroy all COM pointers to shut down your application correctly. this is made setting the interface to nil to calling the varclear function.

Now a basic sample to make WMI query using the COM interface.


{$IFDEF FPC}
 {$MODE DELPHI} {$H+}
{$ENDIF}

{$APPTYPE CONSOLE}

uses
  Windows,
  Variants,
  SysUtils,
  ActiveX,
  JwaWbemCli;

const
  RPC_C_AUTHN_LEVEL_DEFAULT = 0;
  RPC_C_IMP_LEVEL_IMPERSONATE = 3;
  RPC_C_AUTHN_WINNT = 10;
  RPC_C_AUTHZ_NONE = 0;
  RPC_C_AUTHN_LEVEL_CALL = 3;
  EOAC_NONE = 0;

procedure Test_IWbemServices_ExecQuery;
const
  strLocale    = '';
  strUser      = '';
  strPassword  = '';
  strNetworkResource = 'root\cimv2';
  strAuthority       = '';
  WQL                = 'SELECT * FROM Win32_Volume';
var
  FWbemLocator         : IWbemLocator;
  FWbemServices        : IWbemServices;
  FUnsecuredApartment  : IUnsecuredApartment;
  ppEnum               : IEnumWbemClassObject;
  apObjects            : IWbemClassObject;
  puReturned           : ULONG;
  pVal                 : OleVariant;
  pType                : Integer;
  plFlavor             : Integer;
  Succeed              : HRESULT;
begin
  // Set general COM security levels --------------------------
  // Note: If you are using Windows 2000, you need to specify -
  // the default authentication credentials for a user by using
  // a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----
  // parameter of CoInitializeSecurity ------------------------
  if Failed(CoInitializeSecurity(nil, -1, nil, nil, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nil, EOAC_NONE, nil)) then Exit;
  // Obtain the initial locator to WMI -------------------------
  if Succeeded(CoCreateInstance(CLSID_WbemLocator, nil, CLSCTX_INPROC_SERVER, IID_IWbemLocator, FWbemLocator)) then
  try
    // Connect to WMI through the IWbemLocator::ConnectServer method
    if Succeeded(FWbemLocator.ConnectServer(strNetworkResource, strUser, strPassword, strLocale,  WBEM_FLAG_CONNECT_USE_MAX_WAIT, strAuthority, nil, FWbemServices)) then
    try
      // Set security levels on the proxy -------------------------
      if Failed(CoSetProxyBlanket(FWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nil, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nil, EOAC_NONE)) then Exit;
      if Succeeded(CoCreateInstance(CLSID_UnsecuredApartment, nil, CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, FUnsecuredApartment)) then
      try
        // Use the IWbemServices pointer to make requests of WMI
        //Succeed := FWbemServices.ExecQuery('WQL', WQL, WBEM_FLAG_FORWARD_ONLY OR WBEM_FLAG_RETURN_IMMEDIATELY, nil, ppEnum);
        Succeed := FWbemServices.ExecQuery('WQL', WQL, WBEM_FLAG_FORWARD_ONLY, nil, ppEnum);
        if Succeeded(Succeed) then
        begin
          Writeln('Running Wmi Query..Press Enter to exit');
           // Get the data from the query
           while (ppEnum.Next(WBEM_INFINITE, 1, apObjects, puReturned)=0) do
           begin
             apObjects.Get('Caption', 0, pVal, pType, plFlavor);
             Writeln(pVal);
             VarClear(pVal);
           end;
        end
        else
        Writeln(Format('Error executing WQL sentence %x',[Succeed]));
      finally
        FUnsecuredApartment := nil;
      end;
    finally
      FWbemServices := nil;
    end;
  finally
    FWbemLocator := nil;
  end;
end;

begin
  // Initialize COM. ------------------------------------------
  if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED)) then
  try
    Test_IWbemServices_ExecQuery;
  finally
    CoUninitialize();
  end;
  Readln;
end.

And what about the Wmi events?

Ok here i leave the code to manage an async event using the COM WMI API.

Implement the Sink  definition to receive the event

Create a new class which descends from the TInterfacedObject class and the IWbemObjectSink interface, you must implement the Indicate and SetStatus functions.

type
  TWmiEventSink = class(TInterfacedObject, IWbemObjectSink)
  public
    function Indicate(lObjectCount: Longint;  var apObjArray: IWbemClassObject): HRESULT; stdcall;
    function SetStatus(lFlags: Longint; hResult: HRESULT; strParam: WideString; pObjParam: IWbemClassObject): HRESULT; stdcall;
  end;

Initilizate the Sink

Create a instance to the class TWmiEventSink which will handle the received events and use the IUnsecuredApartment.CreateObjectStub function to create a object forwarder sink.

FWmiEventSink := TWmiEventSink.Create;
FUnsecuredApartment.CreateObjectStub(FWmiEventSink, ppStub);

Execute the event

Call the ExecNotificationQueryAsync function passing the sink instance to begin listening the events.

FWbemServices.ExecNotificationQueryAsync('WQL', WQL, WBEM_FLAG_SEND_STATUS, nil, StubSink)

CleanUp

Finally use the CancelAsyncCall function to stop the Event receiver.

FWbemServices.CancelAsyncCall(StubSink);

And this is the full source code to receive the WMI async event


{$IFDEF FPC}
 {$MODE DELPHI} {$H+}
{$ENDIF}

{$APPTYPE CONSOLE}

uses
  Windows,
  Variants,
  SysUtils,
  ActiveX,
  JwaWbemCli;

const
  RPC_C_AUTHN_LEVEL_DEFAULT = 0;
  RPC_C_IMP_LEVEL_IMPERSONATE = 3;
  RPC_C_AUTHN_WINNT = 10;
  RPC_C_AUTHZ_NONE = 0;
  RPC_C_AUTHN_LEVEL_CALL = 3;
  EOAC_NONE = 0;

type
  TWmiEventSink = class(TInterfacedObject, IWbemObjectSink)
  public
    function Indicate(lObjectCount: Longint;  var apObjArray: IWbemClassObject): HRESULT; stdcall;
    function SetStatus(lFlags: Longint; hResult: HRESULT; strParam: WideString; pObjParam: IWbemClassObject): HRESULT; stdcall;
  end;

function TWmiEventSink.Indicate(lObjectCount: Longint; var apObjArray: IWbemClassObject): HRESULT; stdcall;
var
  Instance      : IWbemClassObject;
  wszName       : LPCWSTR;
  pVal          : OleVariant;
  pType         : Integer;
  plFlavor      : Integer;
  lFlags        : Longint;
  Caption, Pid  : string;
begin
  wszName:='TargetInstance';
  lFlags :=0;
  Result := WBEM_S_NO_ERROR;
  if lObjectCount > 0 then
    if Succeeded(apObjArray.Get(wszName, lFlags, pVal, pType, plFlavor)) then
    begin
      Instance := IUnknown(pVal) as IWbemClassObject;
      try
        Instance.Get('Caption', 0, pVal, pType, plFlavor);
        Caption:=pVal;
        VarClear(pVal);

        Instance.Get('ProcessId', 0, pVal, pType, plFlavor);
        Pid:=pVal;
        VarClear(pVal);

        Writeln(Format('Process %s started Pid  %s',[Caption,Pid]));

      finally
        Instance := nil;
      end;
    end;
end;

function TWmiEventSink.SetStatus(lFlags: Longint; hResult: HRESULT; strParam: WideString; pObjParam: IWbemClassObject): HRESULT; stdcall;
begin
  Result := WBEM_S_NO_ERROR;
end;

//detect when a key was pressed in the console window
function KeyPressed:Boolean;
var
  lpNumberOfEvents     : DWORD;
  lpBuffer             : TInputRecord;
  lpNumberOfEventsRead : DWORD;
  nStdHandle           : THandle;
begin
  Result:=false;
  nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
  lpNumberOfEvents:=0;
  GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents);
  if lpNumberOfEvents<> 0 then
  begin
    PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead);
    if lpNumberOfEventsRead <> 0 then
    begin
      if lpBuffer.EventType = KEY_EVENT then
      begin
        if lpBuffer.Event.KeyEvent.bKeyDown then
          Result:=true
        else
          FlushConsoleInputBuffer(nStdHandle);
      end
      else
      FlushConsoleInputBuffer(nStdHandle);
    end;
  end;
end;

//Wmi async event
procedure Test_IWbemServices_ExecNotificationQueryAsync;
const
  strLocale    = '';
  strUser      = '';
  strPassword  = '';
  strNetworkResource = 'root\cimv2';
  strAuthority       = '';
  WQL                = 'SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA "Win32_Process"';
var
  FWbemLocator         : IWbemLocator;
  FWbemServices        : IWbemServices;
  FUnsecuredApartment  : IUnsecuredApartment;
  ppStub               : IUnknown;
  FWmiEventSink        : TWmiEventSink;
  StubSink             : IWbemObjectSink;

begin
  // Set general COM security levels --------------------------
  // Note: If you are using Windows 2000, you need to specify -
  // the default authentication credentials for a user by using
  // a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----
  // parameter of CoInitializeSecurity ------------------------
  if Failed(CoInitializeSecurity(nil, -1, nil, nil, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nil, EOAC_NONE, nil)) then Exit;
  // Obtain the initial locator to WMI -------------------------
  if Succeeded(CoCreateInstance(CLSID_WbemLocator, nil, CLSCTX_INPROC_SERVER, IID_IWbemLocator, FWbemLocator)) then
  try
    // Connect to WMI through the IWbemLocator::ConnectServer method
    if Succeeded(FWbemLocator.ConnectServer(strNetworkResource, strUser, strPassword, strLocale,  WBEM_FLAG_CONNECT_USE_MAX_WAIT, strAuthority, nil, FWbemServices)) then
    try
      // Set security levels on the proxy -------------------------
      if Failed(CoSetProxyBlanket(FWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nil, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nil, EOAC_NONE)) then Exit;
      if Succeeded(CoCreateInstance(CLSID_UnsecuredApartment, nil, CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, FUnsecuredApartment)) then
      try
        FWmiEventSink := TWmiEventSink.Create;
        if Succeeded(FUnsecuredApartment.CreateObjectStub(FWmiEventSink, ppStub)) then
        try
          if Succeeded(ppStub.QueryInterface(IID_IWbemObjectSink, StubSink)) then
          try
            if Succeeded(FWbemServices.ExecNotificationQueryAsync('WQL', WQL, WBEM_FLAG_SEND_STATUS, nil, StubSink)) then
            begin
              Writeln('Listening events...Press any key to exit');
               while not KeyPressed do ;
              FWbemServices.CancelAsyncCall(StubSink);
            end;
          finally
            StubSink := nil;
          end;
        finally
          ppStub := nil;
        end;
      finally
        FUnsecuredApartment := nil;
      end;
    finally
      FWbemServices := nil;
    end;
  finally
    FWbemLocator := nil;
  end;
end;

begin
  // Initialize COM
  if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED)) then
  try
    Test_IWbemServices_ExecNotificationQueryAsync;
  finally
    CoUninitialize();
  end;
  Readln;
end.

Check the source code of this article on Github.


9 Comments

Delphi and WMI Events

The WMI (Windows Management Instrumentation) is mainly know but retrieve hardware and software information using WQL sentences like Select * from Win32_Printer, but the WMI has much more of that. one of the more exciting features is the capability of inform about any particular change in the system using a Event.

Maybe in the past, in delphi forums you are see questions like : How Can I Be Notified When a Process Begins/Ends? How Can I Determine When a Removable Drive Gets Connected/Disconnected? or even How Can I Be Notified Any Time a Network Cable Gets Unplugged? .  All that questions and more can answered using the WMI events.

Today I will show you, how you can do cool things with the WMI events like

Types of Wmi Events

Before to work with the WMI Events we need a brief introduction. exists two types of WMI events intrinsic events and extrinsic events.

Intrinsic Events

An intrinsic event is an event that occurs in response to a change in the WMI data model (the data model or repository is the location where all the WMI information is stored). Each intrinsic event class represents a specific type of change and occurs when WMI or a provider creates, deletes, or modifies a namespace, class, or class instance. For example, if you attach a new printer to the system, you are modifying the Win32_Printer class adding a new instance (record) to the data model, this action  will be reflected by the __InstanceCreationEvent event.

This is the list of the WMI Intrinsic Events

__ClassCreationEvent Notifies a consumer when a class is created.
__ClassDeletionEvent Notifies a consumer when a class is deleted.
__ClassModificationEvent Notifies a consumer when a class is modified.
__InstanceCreationEvent Notifies a consumer when a class instance is created.
__InstanceOperationEvent Notifies a consumer when any instance event occurs, such as creation, deletion, or modification of the instance. You can use this class in queries to get all types events associated with an instance.
__InstanceDeletionEvent Notifies a consumer when an instance is deleted.
__InstanceModificationEvent Notifies a consumer when an instance is modified.
__NamespaceCreationEvent Notifies a consumer when a namespace is created.
__NamespaceDeletionEvent Notifies a consumer when a namespace is deleted.
__NamespaceModificationEvent Notifies a consumer when a namespace is modified.
__ConsumerFailureEvent Notifies a consumer when some other event is dropped due to a failure on the part of an event consumer.
__EventDroppedEvent Notifies a consumer when some other event is dropped instead of being delivered to the requesting event consumer.
__EventQueueOverflowEvent Notifies a consumer when an event is dropped as a result of a delivery queue overflow.
__MethodInvocationEvent Notifies a consumer when a method call event occurs.

Al least which you are writing a WMI provider or something like that, you will use only  the events related to the Instance class like the

the WQL syntax to make a Event Query is

EVENT-WQL = "SELECT"  "FROM" /

OPTIONAL-WITHIN = ["WITHIN" ]
INTERVAL = 1*MODULOREAL
EVENT-WHERE = ["WHERE" ]

EVENT-EXPR = ( ( "ISA"  ) /
               )
              ["GROUP WITHIN"
                    ( ["BY" [ DOT] ]
                      ["HAVING" ]] )
INSTANCE-STATE = "TARGETINSTANCE" / "PREVIOUSINSTANCE"

Now check this simple WQL sentence.

Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA "Win32_LogicalDisk"

In this sentence you are querying for the __InstanceCreationEvent Wmi event occurred in the Win32_LogicalDisk class or in simple words “Tell me when a new instance (record) is added to the Win32_LogicalDisk class”, so this will happen when you insert a new drive in your system.

Notes:

The WITHIN keyword is used to specify the polling interval for the events. A polling interval is the interval that WMI uses as the maximum amount of time that can pass before notification of an event must be delivered.

The TargetInstance is used to reference to the instance of the event class to monitor. Note that we did not use “=” operator . The only valid comparison operator when referecing TargetInstance is  the keyword “ISA”.

Now with this query using the __InstanceDeletionEvent you will be notified when the record is removed from the wmi repository, in this particular case this event will be raised when a logical disk is removed from the system.

Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA "Win32_LogicalDisk"

You can also detect changes over the instance. in this case you will need write a sentence (using the __InstanceModificationEvent event) like so

Select * From __InstanceModificationEvent Within 1 Where TargetInstance ISA "Win32_LogicalDisk"

this event will be raised when occurs a change in the logical disk instance, for example when the Label of the disk is changed.

Extrinsic Events

The extrinsic events represent events that do not directly link to standard WMI model and are implemented for a particular WMI provider. this events are designed to do specific tasks over a particular provider. examples of this events are

The WQL sentence to access these events is even simpler then the necessary to access the Intrinsic events.

check this sample, in this case using the Win32_ProcessStartTrace class we are monitoring when a new process called notepad.exe is started.

Select * From Win32_ProcessStartTrace Where processName="notepad.exe"

Now to check when the process called notepad.exe is stopped.

Select * From Win32_ProcessStopTrace Where processName="notepad.exe"

Receiving a WMI Event

You can receive the WMI events in two modes semisynchronous or asynchronous. The SWbemServices.ExecNotificationQuery  method receive the events in a semisynchronous way y for asynchronous execution you must use the SWbemServices.ExecNotificationQueryAsync method.

semisynchronous

In order to use the SWbemServices.ExecNotificationQuery  method you must follow these steps

  1. Create a instance to the WMI service
  2. Connect to the WMI service
  3. Execute the event in sync mode
  4. Start a loop to receive the events
  5. Receive the event using the SWbemEventSource.NextEvent Method
  6. Check for error code returned and compare with the wbemErrTimedOut ($80043001) value
  7. process the received event

Note : the next samples use late binding to access the wmi.

Check this console application to receive the intrinsic event __InstanceCreationEvent over the Win32_Process class

{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

//detect when a key was pressed in the console window
function KeyPressed:boolean;
var
lpNumberOfEvents     : DWORD;
lpBuffer             : _INPUT_RECORD;
lpNumberOfEventsRead : DWORD;
nStdHandle           : THandle;
begin
  Result:=false;
  nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
  lpNumberOfEvents:=0;
  GetNumberOfConsoleInputEvents(nStdHandle,lpNumberOfEvents);
  if lpNumberOfEvents<> 0 then
  begin
    PeekConsoleInput(nStdHandle,lpBuffer,1,lpNumberOfEventsRead);
    if lpNumberOfEventsRead <> 0 then
    begin
      if lpBuffer.EventType = KEY_EVENT then
      begin
        if lpBuffer.Event.KeyEvent.bKeyDown then
          Result:=true
        else
          FlushConsoleInputBuffer(nStdHandle);
      end
      else
      FlushConsoleInputBuffer(nStdHandle);
    end;
  end;
end;

Procedure  Monitor_Async_Win32_Process;
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
  wbemFlagForwardOnly = $00000020;
  wbemErrTimedout     = $80043001;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FEventResult  : OLEVariant;
begin
  //Create the WMI Scripting Instance
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  //Connect to the WMI service
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  //Execute the event in sync way
  FWbemObjectSet:= FWMIService.ExecNotificationQuery('Select * from __InstanceCreationEvent Within 1 Where TargetInstance ISA "Win32_Process"');
  while not KeyPressed do
  begin
    try
     //receive the event , wai until 100 milliseconds.
     FEventResult := FWbemObjectSet.NextEvent(100);
    except
     on E:EOleException do
     //Check for the timeout and ignore
     if EOleException(E).ErrorCode=HRESULT(wbemErrTimedout) then
       FEventResult:=Null
     else
     raise;
    end;

    //process the received event info
    if not VarIsNull(FEventResult) then
    begin
      Writeln(Format('Caption   %s',[FEventResult.TargetInstance.Caption]));
      Writeln(Format('ProcessId %s',[FEventResult.TargetInstance.ProcessId]));
      Writeln('');
    end;

    //clear the olevariant variable
    FEventResult:=Unassigned;
  end;
end;

var
  Success  : HResult;
begin
 try
    Writeln('Press any key to exit');
    Success:=CoInitialize(nil);
    try
      Monitor_Async_Win32_Process;
    finally
      case Success of
        S_OK, S_FALSE: CoUninitialize;
      end;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

Ok now i want your attention in this part of the code

    //process the received event info
    if not VarIsNull(FEventResult) then
    begin
      Writeln(Format('Caption   %s',[FEventResult.TargetInstance.Caption]));
      Writeln(Format('ProcessId %s',[FEventResult.TargetInstance.ProcessId]));
      Writeln('');
    end;

There you are accessing the properties returned by the SWbemEventSource.NextEvent Method, the main property is the TargetInstance which point to the class used in the WQL sentence  (in this case the Win32_Process). so you can retrieve any property or method exposed by this class.

Check this sample which monitor when the notepad.exe process is started and then kill the process inmediatly.

Procedure  Monitor_Async_Win32_Process;
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
  wbemFlagForwardOnly = $00000020;
  wbemErrTimedout     = $80043001;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FEventResult  : OLEVariant;
begin
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  //monitor only the notepad.exe processes
  FWbemObjectSet:= FWMIService.ExecNotificationQuery('Select * from __InstanceCreationEvent Within 1 Where TargetInstance ISA "Win32_Process" and TargetInstance.Caption="notepad.exe"');
  while not KeyPressed do
  begin
    try
     //receive the event , wai until 100 milliseconds.
     FEventResult := FWbemObjectSet.NextEvent(100);
    except
     on E:EOleException do
     //Check for the timeout and ignore
     if EOleException(E).ErrorCode=HRESULT(wbemErrTimedout) then
       FEventResult:=Null
     else
     raise;
    end;

    //process the received event info
    if not VarIsNull(FEventResult) then
    begin
      Writeln(Format('Caption   %s',[FEventResult.TargetInstance.Caption]));
      Writeln(Format('ProcessId %s',[FEventResult.TargetInstance.ProcessId]));
      Writeln('Killing the Process ');
      FEventResult.TargetInstance.Terminate(0);
    end;

    //clear the olevariant variable
    FEventResult:=Unassigned;
  end;
end;

Ok all this works fine, but in real world applications in very few cases you use a console application to do this kind of task. so to use the SWbemServices.ExecNotificationQuery   method from a VCL application you can encapsulate the logic inside a Thread and using a Windows Message or  callback function you can inform to the main thread which the event arrives.

See the next code which declare a thread called TWmiSyncEventThread to receive the WMI events.

unit uWmiEventThread;

interface

uses
 Classes;

type
   TProcWmiEventThreadeCallBack = procedure(const AObject: OleVariant) of object;
   TWmiSyncEventThread    = class(TThread)
   private
     Success      : HResult;
     FSWbemLocator: OleVariant;
     FWMIService  : OleVariant;
     FEventSource : OleVariant;
     FWbemObject  : OleVariant;
     FCallBack    : TProcWmiEventThreadeCallBack;
     FWQL         : string;
     FServer      : string;
     FUser        : string;
     FPassword    : string;
     FNameSpace   : string;
     TimeoutMs    : Integer;
     procedure RunCallBack;
   public
     Constructor Create(CallBack : TProcWmiEventThreadeCallBack;const Server,User,PassWord,NameSpace,WQL:string;iTimeoutMs : Integer); overload;
     destructor Destroy; override;
     procedure Execute; override;
   end;

implementation

uses
 SysUtils,
 ComObj,
 Variants,
 ActiveX;

constructor TWmiSyncEventThread.Create(CallBack : TProcWmiEventThreadeCallBack;const Server,User,PassWord,NameSpace,WQL:string;iTimeoutMs : Integer);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FCallBack       := CallBack;
  FWQL            := WQL;
  FServer         := Server;
  FUser           := User;
  FPassword       := PassWord;
  FNameSpace      := NameSpace;
  TimeoutMs       := iTimeoutMs;
end;

destructor TWmiSyncEventThread.Destroy;
begin
  FSWbemLocator:=Unassigned;
  FWMIService  :=Unassigned;
  FEventSource :=Unassigned;
  FWbemObject  :=Unassigned;
  inherited;
end;

procedure TWmiSyncEventThread.Execute;
const
  wbemErrTimedout     = $80043001;
//  wbemFlagForwardOnly = $00000020;
begin
  Success := CoInitialize(nil); //CoInitializeEx(nil, COINIT_MULTITHREADED);
  try
    FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
    FWMIService   := FSWbemLocator.ConnectServer(FServer, FNameSpace, FUser, FPassword);
    //FEventSource  := FWMIService.ExecNotificationQuery(FWQL,WideString('WQL'), wbemFlagForwardOnly, null);
    FEventSource  := FWMIService.ExecNotificationQuery(FWQL);
    while not Terminated do
    begin
      try
       FWbemObject := FEventSource.NextEvent(TimeoutMs); //set the max time to wait (ms)
      except
       on E:EOleException do
       if EOleException(E).ErrorCode=HRESULT(wbemErrTimedout) then //Check for the timeout exception   and ignore if exist
        FWbemObject:=Null
       else
       raise;
      end;

      if FindVarData(FWbemObject)^.VType <> varNull then
        Synchronize(RunCallBack);

      FWbemObject:=Unassigned;
    end;
  finally
    case Success of
      S_OK, S_FALSE: CoUninitialize;
    end;
  end;
end;

procedure TWmiSyncEventThread.RunCallBack;
begin
  FCallBack(FWbemObject);
end;

end.

And to use from your own code only you must declare a call back function to receive the result of the event.

  TForm1 = class(TForm)
  private
    WmiThread   : TWmiSyncEventThread;
    procedure  Log(const AObject: OleVariant);
  public
  end;

To begin to receive the event


    WmiThread:=TWmiSyncEventThread.Create(
      Log,
      '.',
      '',
      '',
      'root\CIMV2',
      'Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA "Win32_Process"',
      100);

and the Log function

procedure TForm1.Log(const AObject: OleVariant);
begin
 //do your stuff here
 Memo1.Lines.Add(AObject.TargetInstance.Caption);
end;

finally to stop and free the resources you must call WmiThread.Terminate;

If you wanna play more with this thread check this sample application with source code included.


asynchronous

In order to use the SWbemServices.ExecNotificationQueryAsync  method you must follow these steps

  1. Create an instance to the WMI service
  2. Create an instance to the WMI sink
  3. Assign the event handler for the sink
  4. Connect to the WMI service
  5. Execute the event in async mode
  6. Receive and process the event using the event handler

Note : the next samples uses the WbemScripting_TLB  unit to access the wmi.

Using the WbemScripting_TLB unit (wrapper generated by dephi) for  execute the  ExecNotificationQueryAsync  method is the more affordable way.

Check this short snippet to initializate the WMI service and start to wait for the event in asynchronous mode.


type
TFrmMain = class(TForm)
private
FSink     : TSWbemSink;
FLocator  : ISWbemLocator;
FServices : ISWbemServices;
public
procedure EventReceived(ASender: TObject; const objWbemObject: ISWbemObject; const objWbemAsyncContext: ISWbemNamedValueSet);
end;

procedure TFrmMain.ButtonRunClick(Sender: TObject);
const
 WQL = 'SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA "Win32_Process"';
begin
  FLocator  := CoSWbemLocator.Create;
  //Connect to the WMI service
  FServices := FLocator.ConnectServer('.', 'root\cimv2', '','', '', '', wbemConnectFlagUseMaxWait, nil);
  //create the sink instance
  FSink     := TSWbemSink.Create(self);
  //assign the event handler
  FSink.OnObjectReady := EventReceived;
  //Run the ExecNotificationQueryAsync
  FServices.ExecNotificationQueryAsync(FSink.DefaultInterface,WQL,'WQL', 0, nil, nil);
end;

//The event handler
procedure TFrmMain.EventReceived(ASender: TObject; const objWbemObject: ISWbemObject;  const objWbemAsyncContext: ISWbemNamedValueSet);
var
  PropVal: OLEVariant;
begin
  PropVal := objWbemObject;
  Memo1.Lines.Add(Format('Caption   : %s ',[PropVal.TargetInstance.Caption]));
  Memo1.Lines.Add(Format('ProcessID : %s ',[PropVal.TargetInstance.ProcessID]));
end;

the same rules applied for the above code about access the properties of the TargetInstance when you uses Intrinsic events. download the sample application with source code from here.

Check this list of samples querys to do specific tasks

Determine when a Removable Drive Gets Connected

using the intrinsic event __InstanceCreationEvent and the Win32_LogicalDisk class located in the root\cimv2 namespace

Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA 'Win32_LogicalDisk' AND TargetInstance.DriveType=2

using the intrinsic event __InstanceCreationEvent and the Win32_Volume class located in the root\cimv2 namespace

Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA 'Win32_Volume' AND TargetInstance.DriveType=2

Determine when a Removable Drive Gets Disconnected

using the intrinsic event __InstanceDeletionEvent and the Win32_LogicalDisk class located in the root\cimv2 namespace

Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_LogicalDisk' AND TargetInstance.DriveType=2

using the intrinsic event __InstanceDeletionEvent and the Win32_Volume class located in the root\cimv2 namespace

Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Volume' AND TargetInstance.DriveType=2

Detect when a Process start

using the intrinsic event __InstanceCreationEvent and the Win32_Process class located in the root\cimv2 namespace

Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA 'Win32_Process'

using the extrinsic event Win32_ProcessStartTrace located in the root\cimv2 namespace

Select * From Win32_ProcessStartTrace

Detect when a Process is finished

using the intrinsic event __InstanceDeletionEvent and the Win32_Process class located in the root\cimv2 namespace

Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'

using the extrinsic event Win32_ProcessStopTrace located in the root\cimv2 namespace

Select * From Win32_ProcessStopTrace

Detect when a Thread start

using the intrinsic event __InstanceCreationEvent and the Win32_Thread class located in the root\cimv2 namespace

Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA 'Win32_Thread'

using the extrinsic event Win32_ThreadStartTrace located in the root\cimv2 namespace

Select * From Win32_ThreadStartTrace

Detect when a Thread is finished

using the intrinsic event __InstanceDeletionEvent and the Win32_Thread class located in the root\cimv2 namespace

Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Thread'

using the extrinsic event Win32_ThreadStopTrace located in the root\cimv2 namespace

Select * From Win32_ThreadStopTrace

Detect when a network connection has been lost

using the extrinsic event MSNdis_StatusMediaDisconnect located in the root\wmi namespace

Select * From MSNdis_StatusMediaDisconnect

Detect when a Dll is loaded for an application

using the extrinsic event Win32_ModuleLoadTrace located in the root\cimv2 namespace

Select * From Win32_ModuleLoadTrace

If you want learn more about the WMI events try these articles.


8 Comments

search for installed windows updates using Delphi, WMI and WUA

Sometimes we need determine if a particular windows hotfix or update is installed in the system. to do this task you can use two approaches

WMI (Windows Management Instrumentation)

using the Win32_QuickFixEngineering class, you can retrieve a small system-wide update, commonly referred to as a quick-fix engineering (QFE) update.

check this code which list the updates installed in the system

procedure  GetWin32_QuickFixEngineeringInfo;
const
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_QuickFixEngineering','WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('Caption                        %s',[FWbemObject.Caption]));// String
    //Writeln(Format('CSName                         %s',[FWbemObject.CSName]));// String
    Writeln(Format('Description                    %s',[FWbemObject.Description]));// String
    Writeln(Format('FixComments                    %s',[FWbemObject.FixComments]));// String
    Writeln(Format('HotFixID                       %s',[FWbemObject.HotFixID]));// String
    Writeln(Format('InstallDate                    %s',[FWbemObject.InstallDate]));// Datetime
    Writeln(Format('InstalledBy                    %s',[FWbemObject.InstalledBy]));// String
    Writeln(Format('InstalledOn                    %s',[FWbemObject.InstalledOn]));// String
    Writeln(Format('Name                           %s',[FWbemObject.Name]));// String
    Writeln(Format('ServicePackInEffect            %s',[FWbemObject.ServicePackInEffect]));// String
    Writeln(Format('Status                         %s',[FWbemObject.Status]));// String
    Writeln('');
    FWbemObject:=Unassigned;
  end;
end;

Now to find if a particular update is installed you can check the HotFixID property value (which is the Unique identifier associated with a particular update) and write a function like this

//use in this way ISHotFixID_Installed('KB982799')
function  ISHotFixID_Installed(const HotFixID : string): Boolean;
const
  wbemFlagForwardOnly = $00000020;
  wbemFlagReturnImmediately = $00000010;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM Win32_QuickFixEngineering Where HotFixID="%s"',[HotFixID]),'WQL',wbemFlagForwardOnly OR wbemFlagReturnImmediately);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  Result:= oEnum.Next(1, FWbemObject, iValue) = 0;
end;

Ok this is fine, but Starting with Windows Vista, the Win32_QuickFixEngineering class returns only the updates supplied by Component Based Servicing (CBS), so some updates are not listed.

WUA (Windows Update Agent)

using the Windows Update Agent API is a best option to retrieve the list of updates, you can access the interfaces and objects from this API from delphi importing the wuapi.dll file or creating a late-binding com object using the Microsoft.Update.Session GUID. the next samples uses the late-binding way.

from the MSDN site :

The Windows Update Agent (WUA) API is a set of COM interfaces that enable system administrators and programmers to access Windows Update and Windows Server Update Services (WSUS). Scripts and programs can be written to examine which updates are currently available for a computer, and then you can install or uninstall updates.

to implement a search of the installed updates we need to use the IUpdateSearcher Interface using the Search method setting the search criteria.

check this sample

  //create the Com object instance
  updateSession:= CreateOleObject('Microsoft.Update.Session');
  updateSearcher    := updateSession.CreateUpdateSearcher;
  //set the search criteria, installed =1 means updates that are installed on the destination computer, Type='Software'  retrieve only applications updates
  updateSearchResult:= updateSearcher.Search(Format('IsInstalled = 1 and Type=%s',[QuotedStr('Software')]));

Now to find if a particular update is installed you can parse the Title property of the IUpdate Interface which contains the name of the update like this Security Update for Windows 7 for x64-based Systems (KB978542)

//use in this way ISHotFixID_Installed('KB982799')
function  ISHotFixID_Installed(const HotFixID : string): Boolean;
var
  updateSession      : OleVariant;
  updateSearcher     : OleVariant;
  updateEntry        : OleVariant;
  updateSearchResult : OleVariant;
  UpdateCollection   : OleVariant;
  oEnum              : IEnumvariant;
  iValue             : LongWord;
begin
 result:=False;
  updateSession:= CreateOleObject('Microsoft.Update.Session');
  updateSearcher    := updateSession.CreateUpdateSearcher;
  //this line improves the performance , the online porperty indicates whether the UpdateSearcher goes online to search for updates. so how we are looking for already installed updates we can set this value to false
  updateSearcher.online:=False;
  updateSearchResult:= updateSearcher.Search(Format('IsInstalled = 1 and Type=%s',[QuotedStr('Software')]));
  UpdateCollection  := updateSearchResult.Updates;
  oEnum         := IUnknown(UpdateCollection._NewEnum) as IEnumVariant;
  while oEnum.Next(1, updateEntry, iValue) = 0 do
  begin
    Result:=Pos(HotFixID,updateEntry.Title)>0;
    updateEntry:=Unassigned;
    if Result then break;
  end;
end;

check these another useful functions

Getting the installed updates list

procedure  GetListInstalledUpdates;
var
  updateSession        : OleVariant;
  updateSearcher       : OleVariant;
  updateSearchResult   : OleVariant;
  updateEntry          : OleVariant;
  UpdateCollection     : OleVariant;
  oEnum                : IEnumvariant;
  iValue               : LongWord;
begin
  updateSession:= CreateOleObject('Microsoft.Update.Session');
  updateSearcher := updateSession.CreateUpdateSearcher;
  Writeln('Searching');
  //IUpdateSearcher::Search Method http://msdn.microsoft.com/en-us/library/aa386526%28v=VS.85%29.aspx
  updateSearcher.online:=False;
  updateSearchResult:= updateSearcher.Search(Format('IsInstalled = 1 and Type=%s',[QuotedStr('Software')]));
  UpdateCollection  := updateSearchResult.Updates;
  oEnum         := IUnknown(UpdateCollection._NewEnum) as IEnumVariant;
  //IUpdate Interface http://msdn.microsoft.com/en-us/library/aa386099%28v=VS.85%29.aspx
  while oEnum.Next(1, updateEntry, iValue) = 0 do
  begin
    Writeln(updateEntry.Title);
    updateEntry:=Unassigned;
  end;
  Writeln('Done');
end;

Getting the not installed updates list (slow because need to check online)

procedure  GetListNotInstalledUpdates;
var
  updateSession        : OleVariant;
  updateSearcher       : OleVariant;
  updateSearchResult   : OleVariant;
  updateEntry          : OleVariant;
  UpdateCollection     : OleVariant;
  oEnum                : IEnumvariant;
  iValue               : LongWord;
begin
  updateSession:= CreateOleObject('Microsoft.Update.Session');
  updateSearcher := updateSession.CreateUpdateSearcher;
  Writeln('Searching');
  updateSearchResult:= updateSearcher.Search(Format('IsInstalled = 0 and Type=%s',[QuotedStr('Software')]));
  UpdateCollection  := updateSearchResult.Updates;
  oEnum         := IUnknown(UpdateCollection._NewEnum) as IEnumVariant;
  while oEnum.Next(1, updateEntry, iValue) = 0 do
  begin
    Writeln(updateEntry.Title);
    updateEntry:=Unassigned;
  end;
  Writeln('Done');
end;

Getting the hidden installed updates list

procedure  GetListInstalledHiddenUpdates;
var
  updateSession        : OleVariant;
  updateSearcher       : OleVariant;
  updateSearchResult   : OleVariant;
  updateEntry          : OleVariant;
  UpdateCollection     : OleVariant;
  oEnum                : IEnumvariant;
  iValue               : LongWord;
begin
  updateSession:= CreateOleObject('Microsoft.Update.Session');
  updateSearcher := updateSession.CreateUpdateSearcher;
  Writeln('Searching');
  updateSearcher.online:=False;
  updateSearchResult:= updateSearcher.Search(Format('IsHidden=1 and IsInstalled = 1 and Type=%s',[QuotedStr('Software')]));
  UpdateCollection  := updateSearchResult.Updates;
  oEnum         := IUnknown(UpdateCollection._NewEnum) as IEnumVariant;
  while oEnum.Next(1, updateEntry, iValue) = 0 do
  begin
    Writeln(updateEntry.Title);
    updateEntry:=Unassigned;
  end;
  Writeln('Done');
end;


1 Comment

Change the drive letter using WMI and Delphi

Today i will show a short snippet to change the letter from a drive (Volume) using the WMI. the key is use the Win32_Volume class and set the value of the DriveLetter property. this property is read/write so we can update directly the value and then call the Put_ method of the SWbemObject object.

Check out this sample project

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ActiveX,
  ComObj;

procedure  ChangeDriveLetter(OldDrive,NewDrive:Char);
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM Win32_Volume Where DriveLetter=%s',[QuotedStr(OldDrive+':')]),'WQL',0);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  if oEnum.Next(1, FWbemObject, iValue) = 0 then
  begin
    //Assign the New letter
    FWbemObject.DriveLetter:=NewDrive+':';
    //Apply the changes
    FWbemObject.Put_();
  end;
end;

begin
 try
    CoInitialize(nil);
    try
      //This will change the letter of the drive E to Z
      ChangeDriveLetter('E','Z');
      Readln;
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
    begin
        Writeln(E.Classname, ':', E.Message);
        Readln;
    end;
  end;
end.


21 Comments

Getting the installed Antivirus, AntiSpyware and Firewall software using Delphi and the WMI

The WMI allow you to get the installed Antivirus, AntiSpyware and Firewall (third party)  software using the root\SecurityCenter or the root\SecurityCenter2 namespaces and the AntiVirusProduct , AntiSpywareProduct, FirewallProduct classes.

First you must know which these classes and namespaces are not documented by Microsoft and only are supported in Windows Desktops editions (Windows XP, Windows Vista and Windows 7).
Now depending of the Windows version the properties retrieved by the the same class can change. this is a summary of the classes and properties availables depending of the windows version

Windows XP

Namespace : SecurityCenter
Classes availables: AntiVirusProduct, FirewallProduct

AntiVirusProduct-Properties

  • companyName
  • displayName
  • enableOnAccessUIMd5Hash
  • enableOnAccessUIParameters
  • instanceGuid
  • onAccessScanningEnabled
  • pathToEnableOnAccessUI
  • pathToUpdateUI
  • productUptoDate
  • updateUIMd5Hash
  • updateUIParameters
  • versionNumber

FirewallProduct-Properties

  • companyName
  • displayName
  • enabled
  • enableUIMd5Hash
  • enableUIParameters
  • instanceGuid
  • pathToEnableUI
  • versionNumber

Windows Vista and Windows 7

Namespace : SecurityCenter2
Classes availables : AntiVirusProduct, AntiSpywareProduct, FirewallProduct

AntiVirusProduct, AntiSpywareProduct, FirewallProduct – Properties

  • displayName
  • instanceGuid
  • pathToSignedProductExe
  • pathToSignedReportingExe
  • productState

This is a sample project which determine the Antivirus, AntiSpyware and Firewall software installed in the system.

program GetSecurityCenterInfo;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Windows,
  ActiveX,
  ComObj,
  Variants;

type
  TSecurityCenterProduct = (AntiVirusProduct,AntiSpywareProduct,FirewallProduct);
const
  WmiRoot='root';
  WmiClassSCProduct     : array [TSecurityCenterProduct] of string = ('AntiVirusProduct','AntiSpywareProduct','FirewallProduct');
  WmiNamespaceSCProduct : array [Boolean] of string = ('SecurityCenter','SecurityCenter2');

function VerSetConditionMask(dwlConditionMask: int64;dwTypeBitMask: DWORD; dwConditionMask: Byte): int64; stdcall; external kernel32;

{$IFDEF UNICODE}
function VerifyVersionInfo(var LPOSVERSIONINFOEX : OSVERSIONINFOEX;dwTypeMask: DWORD;dwlConditionMask: int64): BOOL; stdcall; external kernel32 name 'VerifyVersionInfoW';
{$ELSE}
function VerifyVersionInfo(var LPOSVERSIONINFOEX : OSVERSIONINFOEX;dwTypeMask: DWORD;dwlConditionMask: int64): BOOL; stdcall; external kernel32 name 'VerifyVersionInfoA';
{$ENDIF}

//verifies that the application is running on Windows 2000 Server or a later server, such as Windows Server 2003 or Windows Server 2008.
function Is_Win_Server : Boolean;
const
   VER_NT_SERVER      = $0000003;
   VER_EQUAL          = 1;
   VER_GREATER_EQUAL  = 3;
var
   osvi             : OSVERSIONINFOEX;
   dwlConditionMask : DWORDLONG;
   op               : Integer;
begin
   dwlConditionMask := 0;
   op:=VER_GREATER_EQUAL;

   ZeroMemory(@osvi, sizeof(OSVERSIONINFOEX));
   osvi.dwOSVersionInfoSize := sizeof(OSVERSIONINFOEX);
   osvi.dwMajorVersion := 5;
   osvi.dwMinorVersion := 0;
   osvi.wServicePackMajor := 0;
   osvi.wServicePackMinor := 0;
   osvi.wProductType := VER_NT_SERVER;

   dwlConditionMask:=VerSetConditionMask( dwlConditionMask, VER_MAJORVERSION, op );
   dwlConditionMask:=VerSetConditionMask( dwlConditionMask, VER_MINORVERSION, op );
   dwlConditionMask:=VerSetConditionMask( dwlConditionMask, VER_SERVICEPACKMAJOR, op );
   dwlConditionMask:=VerSetConditionMask( dwlConditionMask, VER_SERVICEPACKMINOR, op );
   dwlConditionMask:=VerSetConditionMask( dwlConditionMask, VER_PRODUCT_TYPE, VER_EQUAL );

   Result:=VerifyVersionInfo(osvi,VER_MAJORVERSION OR VER_MINORVERSION OR
      VER_SERVICEPACKMAJOR OR VER_SERVICEPACKMINOR OR VER_PRODUCT_TYPE, dwlConditionMask);
end;

procedure  GetSCProductInfo(SCProduct:TSecurityCenterProduct);
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
  osVerInfo     : TOSVersionInfo;
begin
  osVerInfo.dwOSVersionInfoSize:=SizeOf(TOSVersionInfo);
  GetVersionEx(osVerInfo);
  if (SCProduct=AntiSpywareProduct) and (osVerInfo.dwMajorVersion<6)  then exit;   FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');   FWMIService   := FSWbemLocator.ConnectServer('localhost',Format('%s\%s',[WmiRoot,WmiNamespaceSCProduct[osVerInfo.dwMajorVersion>=6]]), '', '');
  FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM %s',[WmiClassSCProduct[SCProduct]]),'WQL',0);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    if osVerInfo.dwMajorVersion>=6 then  //windows vista or newer
    begin
      Writeln(Format('displayName                    %s',[FWbemObject.displayName]));// String
      Writeln(Format('instanceGuid                   %s',[FWbemObject.instanceGuid]));// String
      Writeln(Format('pathToSignedProductExe         %s',[FWbemObject.pathToSignedProductExe]));// String
      Writeln(Format('pathToSignedReportingExe       %s',[FWbemObject.pathToSignedReportingExe]));// String
      Writeln(Format('productState                   %s',[FWbemObject.productState]));// Uint32
    end
    else
    begin
     case SCProduct of

        AntiVirusProduct :
         begin
            Writeln(Format('companyName                    %s',[FWbemObject.companyName]));// String
            Writeln(Format('displayName                    %s',[FWbemObject.displayName]));// String
            Writeln(Format('enableOnAccessUIMd5Hash        %s',[FWbemObject.enableOnAccessUIMd5Hash]));// Uint8
            Writeln(Format('enableOnAccessUIParameters     %s',[FWbemObject.enableOnAccessUIParameters]));// String
            Writeln(Format('instanceGuid                   %s',[FWbemObject.instanceGuid]));// String
            Writeln(Format('onAccessScanningEnabled        %s',[FWbemObject.onAccessScanningEnabled]));// Boolean
            Writeln(Format('pathToEnableOnAccessUI         %s',[FWbemObject.pathToEnableOnAccessUI]));// String
            Writeln(Format('pathToUpdateUI                 %s',[FWbemObject.pathToUpdateUI]));// String
            Writeln(Format('productUptoDate                %s',[FWbemObject.productUptoDate]));// Boolean
            Writeln(Format('updateUIMd5Hash                %s',[FWbemObject.updateUIMd5Hash]));// Uint8
            Writeln(Format('updateUIParameters             %s',[FWbemObject.updateUIParameters]));// String
            Writeln(Format('versionNumber                  %s',[FWbemObject.versionNumber]));// String
         end;

       FirewallProduct  :
         begin
            Writeln(Format('companyName                    %s',[FWbemObject.companyName]));// String
            Writeln(Format('displayName                    %s',[FWbemObject.displayName]));// String
            Writeln(Format('enabled                        %s',[FWbemObject.enabled]));// Boolean
            Writeln(Format('enableUIMd5Hash                %s',[FWbemObject.enableUIMd5Hash]));// Uint8
            Writeln(Format('enableUIParameters             %s',[FWbemObject.enableUIParameters]));// String
            Writeln(Format('instanceGuid                   %s',[FWbemObject.instanceGuid]));// String
            Writeln(Format('pathToEnableUI                 %s',[FWbemObject.pathToEnableUI]));// String
            Writeln(Format('versionNumber                  %s',[FWbemObject.versionNumber]));// String
         end;
     end;
    end;
    Writeln('');
    FWbemObject:=Unassigned;
  end;
end;

begin
 try
    if Is_Win_Server then
    begin
     Writeln('Sorry this app only can run in desktop operating systems.');
     Halt;
    end;

    CoInitialize(nil);
    try
      Writeln('AntiVirus Info');
      Writeln('--------------');
      GetSCProductInfo(AntiVirusProduct);
      Writeln('AntiSpyware Info');
      Writeln('----------------');
      GetSCProductInfo(AntiSpywareProduct);
      Writeln('Firewall Info');
      Writeln('-------------');
      GetSCProductInfo(FirewallProduct);
      Readln;
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
    begin
        Writeln(E.Classname, ':', E.Message);
        Readln;
    end;
  end;
end.

And here is the result of the app.


36 Comments

Making a PING with Delphi and the WMI

Typically we use the IcmpSendEcho function or a component like TIdIcmpClient to make a ping request from Delphi. Today I will show you another way to do this using the WMI (Windows Management Instrumentation).

The WMI class which allow you to make a ping request is Win32_PingStatus, to use this class you only need to pass the parameter Address value in your WQL sentence , the form of the Address parameter can be either the computer name (ACCOUNT-PC), IPv4 address (192.168.154.102), or IPv6 address (2010:836B:4179::836B:4179).

SELECT * FROM Win32_PingStatus where Address='www.google.com'

Some of the advantages of use this class to make a ping is which supports IPv4 addresses and IPv6 addresses (Starting with Windows Vista) , and you can set the ping parameters in a single WQL sentence.

For example if you want send a Buffer of 64 bytes (instead of the 32 default size) and resolve the address of the host server you only need to write a sentence like this :

SELECT * FROM Win32_PingStatus where Address='192.168.1.221' AND BufferSize=64 AND ResolveAddressNames=TRUE

Now check this sample console application.

program WMIPing;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ActiveX,
  ComObj,
  Variants;

function GetStatusCodeStr(statusCode:integer) : string;
begin
  case statusCode of
    0     : Result:='Success';
    11001 : Result:='Buffer Too Small';
    11002 : Result:='Destination Net Unreachable';
    11003 : Result:='Destination Host Unreachable';
    11004 : Result:='Destination Protocol Unreachable';
    11005 : Result:='Destination Port Unreachable';
    11006 : Result:='No Resources';
    11007 : Result:='Bad Option';
    11008 : Result:='Hardware Error';
    11009 : Result:='Packet Too Big';
    11010 : Result:='Request Timed Out';
    11011 : Result:='Bad Request';
    11012 : Result:='Bad Route';
    11013 : Result:='TimeToLive Expired Transit';
    11014 : Result:='TimeToLive Expired Reassembly';
    11015 : Result:='Parameter Problem';
    11016 : Result:='Source Quench';
    11017 : Result:='Option Too Big';
    11018 : Result:='Bad Destination';
    11032 : Result:='Negotiating IPSEC';
    11050 : Result:='General Failure'
    else
    result:='Unknow';
  end;
end;


//The form of the Address parameter can be either the computer name (wxyz1234), IPv4 address (192.168.177.124), or IPv6 address (2010:836B:4179::836B:4179).
procedure  Ping(const Address:string;Retries,BufferSize:Word);
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
  i             : Integer;

  PacketsReceived : Integer;
  Minimum         : Integer;
  Maximum         : Integer;
  Average         : Integer;
begin;
  PacketsReceived:=0;
  Minimum        :=0;
  Maximum        :=0;
  Average        :=0;
  Writeln('');
  Writeln(Format('Pinging %s with %d bytes of data:',[Address,BufferSize]));
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  //FWMIService   := FSWbemLocator.ConnectServer('192.168.52.130', 'root\CIMV2', 'user', 'password');
  for i := 0 to Retries-1 do
  begin
    FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM Win32_PingStatus where Address=%s AND BufferSize=%d',[QuotedStr(Address),BufferSize]),'WQL',0);
    oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
    if oEnum.Next(1, FWbemObject, iValue) = 0 then
    begin
      if FWbemObject.StatusCode=0 then
      begin
        if FWbemObject.ResponseTime>0 then
          Writeln(Format('Reply from %s: bytes=%s time=%sms TTL=%s',[FWbemObject.ProtocolAddress,FWbemObject.ReplySize,FWbemObject.ResponseTime,FWbemObject.TimeToLive]))
        else
          Writeln(Format('Reply from %s: bytes=%s time=<1ms TTL=%s',[FWbemObject.ProtocolAddress,FWbemObject.ReplySize,FWbemObject.TimeToLive]));

        Inc(PacketsReceived);

        if FWbemObject.ResponseTime>Maximum then
        Maximum:=FWbemObject.ResponseTime;

        if Minimum=0 then
        Minimum:=Maximum;

        if FWbemObject.ResponseTime<Minimum then
        Minimum:=FWbemObject.ResponseTime;

        Average:=Average+FWbemObject.ResponseTime;
      end
      else
      if not VarIsNull(FWbemObject.StatusCode) then
        Writeln(Format('Reply from %s: %s',[FWbemObject.ProtocolAddress,GetStatusCodeStr(FWbemObject.StatusCode)]))
      else
        Writeln(Format('Reply from %s: %s',[Address,'Error processing request']));
    end;
    FWbemObject:=Unassigned;
    FWbemObjectSet:=Unassigned;
    //Sleep(500);
  end;

  Writeln('');
  Writeln(Format('Ping statistics for %s:',[Address]));
  Writeln(Format('    Packets: Sent = %d, Received = %d, Lost = %d (%d%% loss),',[Retries,PacketsReceived,Retries-PacketsReceived,Round((Retries-PacketsReceived)*100/Retries)]));
  if PacketsReceived>0 then
  begin
   Writeln('Approximate round trip times in milli-seconds:');
   Writeln(Format('    Minimum = %dms, Maximum = %dms, Average = %dms',[Minimum,Maximum,Round(Average/PacketsReceived)]));
  end;
end;


begin
 try
    CoInitialize(nil);
    try
      //Ping('192.168.52.130',4,32);
      Ping('theroadtodelphi.wordpress.com',4,32);
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

And the output


14 Comments

A New project – Delphi (Object Pascal) WMI class code generator

The lasts weeks I’ve been working in a new project, called Delphi WMI class code generator. let me tell you about it.

The WMI (Windows Management Instrumentation) is formed by many classes, this classes exposes properties and methods. Also each class, property and method have qualifiers which are something like attributes, these qualifiers include descriptions about the classes, method, parameters or properties, types and many more useful information.

Now to access the properties of a wmi class from object pascal code is a very easy task, as was shown in this post, but by the other side to access the methods is little more complicated, because you need to known if the method is static or dynamic. also you must deal in some cases with complicated parameters which must be variants arrays, objects or datetime (in UTC format). and finally some of these parameters can be optional. so if you are only an occasional user of the WMI you must figure out a lot of thinks before to use it.

Because that and to the experience gained when I wrote the WMI Delphi Code Creator application, I decided to go a couple of steps forward and create tool which facilitate the access to the properties and methods exposed by the WMI classes from Object Pascal code.

The result was a code generator which parse the very rich meta-data of the wmi classes and extract the properties and methods and convert into a Object pascal class.

Now Let me show a sample code generated by the tool for the Win32_Share Wmi class.

/// <summary>
/// Unit generated using the Delphi Wmi class generator tool, Copyright Rodrigo Ruz V. 2010
/// Application version 0.1.0.120
/// WMI version 7600.16385
/// Creation Date 24-12-2010 09:38:11
/// Namespace root\CIMV2 Class Win32_Share
/// MSDN info about this class http://msdn2.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/Win32_Share.asp
/// </summary>

{$IFDEF FPC}
 {$MODE DELPHI} {$H+}
 {$DEFINE OLD_DELPHI}
{$ENDIF}

unit uWin32_Share;

interface

uses
 Classes,
 Activex,
 Variants,
 ComObj,
 uWmiDelphiClass;

type
{$IFDEF FPC}
 Cardinal=Longint;
 Int64=Integer;
 Word=Longint;
{$ENDIF}
{$IFNDEF FPC}
 {$IF CompilerVersion <= 15}
 {$DEFINE OLD_DELPHI}
 {$IFEND}
{$ENDIF}
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Win32_Share class represents a shared resource on a Win32 system. This may be a disk drive, printer, interprocess communication, or other shareable device.
 /// Example: C:\PUBLIC.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 TWin32_Share=class(TWmiClass)
 private
 FAccessMask                         : Cardinal;
 FAllowMaximum                       : Boolean;
 FCaption                            : String;
 FDescription                        : String;
 FInstallDate                        : TDateTime;
 FMaximumAllowed                     : Cardinal;
 FName                               : String;
 FPath                               : String;
 FStatus                             : String;
 FType                               : Cardinal;
 public
 constructor Create(LoadWmiData : boolean=True); overload;
 destructor Destroy;Override;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// This property has been deprecated in favour of the GetAccessMask method of this
 /// class due to the expense of calling GetEffectiveRightsFromAcl. The value will
 /// be set to NULL
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property AccessMask : Cardinal read FAccessMask;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The AllowMaximum property indicates whether the number of concurrent users for this resource has been limited.
 /// Values: TRUE or FALSE. A value of TRUE indicates the number of concurrent users of this resource has not been limited and the value in the MaximumAllowed property is ignored.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property AllowMaximum : Boolean read FAllowMaximum;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Caption property is a short textual description (one-line string) of the
 /// object.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Caption : String read FCaption;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Description property provides a textual description of the object.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Description : String read FDescription;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The InstallDate property is datetime value indicating when the object was
 /// installed. A lack of a value does not indicate that the object is not installed.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property InstallDate : TDateTime read FInstallDate;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The MaximumAllowed property indicates the limit on the maximum number of users allowed to use this resource concurrently. The value is only valid if the AllowMaximum member set to FALSE
 /// Example: 10.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property MaximumAllowed : Cardinal read FMaximumAllowed;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Name property indicates the alias given to a path set up as a share on a  Win32 system.
 /// Example: public.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Name : String read FName;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Path property indicates the local path of the Win32 share.
 /// Example: C:\Program Files
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Path : String read FPath;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Status property is a string indicating the current status of the object.
 /// Various operational and non-operational statuses can be defined. Operational
 /// statuses are "OK", "Degraded" and "Pred Fail". "Pred Fail" indicates that an
 /// element may be functioning properly but predicting a failure in the near
 /// future. An example is a SMART-enabled hard drive. Non-operational statuses can
 /// also be specified. These are "Error", "Starting", "Stopping" and "Service". The
 /// latter, "Service", could apply during mirror-resilvering of a disk, reload of a
 /// user permissions list, or other administrative work. Not all such work is on-
 /// line, yet the managed element is neither "OK" nor in one of the other states.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property Status : String read FStatus;
 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// The Type property specifies the type of resource being shared. Types include
 /// disk drives, print queues, interprocess communications (IPC), and general
 /// devices.
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 property {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF} : Cardinal read FType;
 function Create(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal;const Name : String;const Password : String;const Path : String;const {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF} : Cardinal): Integer;overload;
 function SetShareInfo(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal): Integer;
 function GetAccessMask: Integer;
 function Delete: Integer;
 procedure SetCollectionIndex(Index : Integer); override;
 end;

 {$IFDEF UNDEF}{$REGION 'Documentation'}{$ENDIF}
 /// <summary>
 /// Return the description for the value of the property TWin32_Share.Type
 /// </summary>
 {$IFDEF UNDEF}{$ENDREGION}{$ENDIF}
 function GetTypeAsString(const APropValue:Cardinal) : string;

implementation

function GetTypeAsString(const APropValue:Cardinal) : string;
begin
Result:='';
 case APropValue of
 0 : Result:='Disk Drive';
 1 : Result:='Print Queue';
 2 : Result:='Device';
 3 : Result:='IPC';
 2147483648 : Result:='Disk Drive Admin';
 2147483649 : Result:='Print Queue Admin';
 2147483650 : Result:='Device Admin';
 2147483651 : Result:='IPC Admin';
 end;
end;

{TWin32_Share}

constructor TWin32_Share.Create(LoadWmiData : boolean=True);
begin
 inherited Create(LoadWmiData,'root\CIMV2','Win32_Share');
end;

destructor TWin32_Share.Destroy;
begin
 inherited;
end;

procedure TWin32_Share.SetCollectionIndex(Index : Integer);
begin
 if (Index>=0) and (Index<=FWmiCollection.Count-1) and (FWmiCollectionIndex<>Index) then
 begin
 FWmiCollectionIndex:=Index;
 FAccessMask          := VarCardinalNull(inherited Value['AccessMask']);
 FAllowMaximum        := VarBoolNull(inherited Value['AllowMaximum']);
 FCaption             := VarStrNull(inherited Value['Caption']);
 FDescription         := VarStrNull(inherited Value['Description']);
 FInstallDate         := VarDateTimeNull(inherited Value['InstallDate']);
 FMaximumAllowed      := VarCardinalNull(inherited Value['MaximumAllowed']);
 FName                := VarStrNull(inherited Value['Name']);
 FPath                := VarStrNull(inherited Value['Path']);
 FStatus              := VarStrNull(inherited Value['Status']);
 FType                := VarCardinalNull(inherited Value['Type']);
 end;
end;

//static, OutParams=1, InParams>0
function TWin32_Share.Create(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal;const Name : String;const Password : String;const Path : String;const {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF} : Cardinal): Integer;
var
 objInParams                : OleVariant;
 objOutParams               : OleVariant;
begin
 objInParams                 := GetInstanceOf.Methods_.Item('Create').InParameters.SpawnInstance_();
 objInParams.Properties_.Item('Access').Value  := Access;
 objInParams.Properties_.Item('Description').Value  := Description;
 objInParams.Properties_.Item('MaximumAllowed').Value  := MaximumAllowed;
 objInParams.Properties_.Item('Name').Value  := Name;
 objInParams.Properties_.Item('Password').Value  := Password;
 objInParams.Properties_.Item('Path').Value  := Path;
 objInParams.Properties_.Item('Type').Value  := {$IFDEF OLD_DELPHI}_Type{$ELSE}&Type{$ENDIF};
 objOutParams                := WMIService.ExecMethod(WmiClass, 'Create', objInParams, 0, GetNullValue);
 Result := VarIntegerNull(objOutParams.ReturnValue);
end;

//not static, OutParams=1, InParams>0
function TWin32_Share.SetShareInfo(const Access : OleVariant;const Description : String;const MaximumAllowed : Cardinal): Integer;
var
 ReturnValue : OleVariant;
begin
 ReturnValue := GetInstanceOf.SetShareInfo(Access,Description,MaximumAllowed);
 Result      := VarIntegerNull(ReturnValue);
end;

//not static, OutParams=1, InParams=0
function TWin32_Share.GetAccessMask: integer;
var
 ReturnValue : OleVariant;
begin
 ReturnValue := GetInstanceOf.GetAccessMask;
 Result      := VarIntegerNull(ReturnValue);
end;

//not static, OutParams=1, InParams=0
function TWin32_Share.Delete: integer;
var
 ReturnValue : OleVariant;
begin
 ReturnValue := GetInstanceOf.Delete;
 Result      := VarIntegerNull(ReturnValue);
end;
end.

as you can see the generated code is a full documented class compatible with the delphi help insight feature, available since Delphi 2005.

check this screen-shot which show the help insight for the Getowner method of the Win32_Process class.

This tool not only facilitate the access to the wmi, also give you information about every single WMI class, method and property.

here some features of the application

  • The code generated is compatible Delphi 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE and the Free Pascal Compiler 2.2.4 (win32)
  • Create full documented classes compatible with the help insight feature, available since Delphi 2005.
    Note : the language of the description of the methods, parameters and properties depends on of the language of the windows where you generate the units.
  • Create additional helper functions to retrieve the description of the returned values for the properties and functions.
  • Support access to the WMI of the remote computers.

Now see this sample application which uses a class generated by the tool to access the BIOS information of a Remote PC.

program TestRemote;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  uWmiDelphiClass in '..\..\uWmiDelphiClass.pas', //the base class must be always included
  uWin32_BIOS in '..\..\root_CIMV2\uWin32_BIOS.pas'; //the class with the BIOs information

var
  RemoteBiosInfo : TWin32_BIOS;
  i              : integer;
begin
   try
     RemoteBiosInfo:=TWin32_BIOS.Create(False); //Create a instance of the TWin32_BIOS, the false value indicate which not load the Values when calls the constructor.
     try

       RemoteBiosInfo.WmiServer:='192.168.217.128'; //the remote pc name or IP
       RemoteBiosInfo.WmiUser  :='Administrator'; //the user used to establish the connection
       RemoteBiosInfo.WmiPass  :='password'; //the password
       RemoteBiosInfo.LoadWmiData; //now load the the data of the class

       if RemoteBiosInfo.WmiConnected then  //check if the connection was established
       begin
         Writeln('Serial Number       '+RemoteBiosInfo.SerialNumber);
         Writeln('BuildNumber         '+RemoteBiosInfo.BuildNumber);
         if RemoteBiosInfo.BIOSVersion.Count>0 then
         Writeln('Version             '+RemoteBiosInfo.BIOSVersion[0]);
         Writeln('Identification Code '+RemoteBiosInfo.IdentificationCode);
         Writeln('Manufacturer        '+RemoteBiosInfo.Manufacturer);
         Writeln('SoftwareElementID   '+RemoteBiosInfo.SoftwareElementID);
         Writeln('Release Date        '+DateToStr(RemoteBiosInfo.ReleaseDate));
         Writeln('Install Date        '+DateToStr(RemoteBiosInfo.InstallDate));
         Writeln('Target S.O          '+GetTargetOperatingSystemAsString(RemoteBiosInfo.TargetOperatingSystem));
         Writeln('Soft. element state '+GetSoftwareElementStateAsString(RemoteBiosInfo.SoftwareElementState));

         Writeln('');
         Writeln('Bios Characteristics');
         Writeln('--------------------');
         for i:=Low(RemoteBiosInfo.BiosCharacteristics)  to High(RemoteBiosInfo.BiosCharacteristics) do
          Writeln(GetBiosCharacteristicsAsString(RemoteBiosInfo.BiosCharacteristics[i]));
       end
       else
       Writeln('No connected');
     finally
      RemoteBiosInfo.Free;
     end;
   except
    on E:Exception do
     Writeln(E.Classname, ': ', E.Message);
   end;

 Readln;
end.

You can found more information about the internals, the full source code, demos and samples of this tool in the google code project page.

See you, and happy new year.