Search code examples
c#wmi

How do I filter a WMI search on what I think is a nested property?


I am using the following to get a list of loaded dependents or modules via WMI, but am having issues geting my search string correct, basically I need to target a specific dependent process via its handle, and the handle property seems to be nested inside a ManagementObject.

var wmiQueryString = string.Format("select * from CIM_ProcessExecutable WHERE Dependent.Handle=\"{0}\"",procID);
using (var searcher = new ManagementObjectSearcher(string.Format(wmiQueryString)))
using (var results = searcher.Get())
{
    foreach (var item in results.Cast<ManagementObject>())
    {
        try
        {
            var dependent = new ManagementObject((string)item["Dependent"]);
            Console.WriteLine(new FileInfo((string)dependent["Name"]).FullName);
        }
        catch (System.Management.ManagementException ex)
        {
            // Process does not exist anymore
        }
    }
}

Using just "Dependent.Handle" does not seem to work, every variation I have tried has resulted in invalid query string exceptions. I assume because the searcher has no understanding of the object structure?

I could load and filter through the data in my C#, but for performance reasons I would like to do the filter in the WMI query.

Updated code based on answer below:

        var wmiQueryString = string.Format("ASSOCIATORS OF {{Win32_Process.Handle=\"{0}\" }} WHERE ResultClass = CIM_ProcessExecutable", procID);
        using (var searcher = new ManagementObjectSearcher(wmiQueryString))
        using (var results = searcher.Get())
        {
            foreach (ManagementObject item in results) // This throws System.Management.ManagementException: 'Invalid object path '
            {
                foreach (PropertyData prop in item.Properties) // At this point this is just here for testing, but this is never reached anyway as the exception occurs prior to the iteration.
                {
                    Console.WriteLine("{0}: {1}", prop.Name, prop.Value);
                }

                //var dependent = item["Dependent"] as ManagementObject;
                //Console.WriteLine(new FileInfo((string)dependent["Name"]).FullName);
            }
        }

This however throws System.Management.ManagementException: 'Invalid object path ' on the indicated line. But this may just be from the previous line, which may still be indicative of a bad query string.


Solution

  • Turns out, the secret sauce is references of not associators of.

    var wmiQueryString = string.Format( "references of {{win32_process.Handle={0}}}", handle );
    using ( var searcher = new ManagementObjectSearcher( wmiQueryString ) )
    using ( var results = searcher.Get( ) )
    {
      foreach ( ManagementObject item in results )
      {
        Console.WriteLine( item.ClassPath ); //--> turns out this is the cim_processexecutalbe
    
        //--> and these are it's properties...with references to cim_datafile...  
        foreach ( PropertyData prop in item.Properties )
        {
          Console.WriteLine( "{0}: {1}", prop.Name, prop.Value );
        }
      }
    }
    

    This gets you the CIM_ProcessExecutables properties:

    \\CLAYDEV\root\cimv2:Win32_SessionProcess
    Antecedent: \\.\root\cimv2:Win32_LogonSession.LogonId="999"
    Dependent: \\.\root\cimv2:Win32_Process.Handle="628"
    \\CLAYDEV\root\cimv2:Win32_SystemProcesses
    GroupComponent: \\CLAYDEV\root\cimv2:Win32_ComputerSystem.Name="CLAYDEV"
    PartComponent: \\CLAYDEV\root\cimv2:Win32_Process.Handle="628"
    \\CLAYDEV\root\cimv2:CIM_ProcessExecutable
    Antecedent: \\CLAYDEV\root\cimv2:CIM_DataFile.Name="C:\\WINDOWS\\system32\\winlogon.exe"
    BaseAddress: 140696226496512
    Dependent: \\CLAYDEV\root\cimv2:Win32_Process.Handle="628"
    GlobalProcessCount:
    ModuleInstance: 1687814144
    ProcessCount: 0
    ....
    

    It also turns out - as Mateo points out in the comments, that references of and associators of are kinda finicky about formatting. Can't have extra spaces inside the {}. I didn't know that.

    You can use associators of, too. If you get the syntax just so...and you refer to the associated type (not the associating type). What I mean is CIM_ProcessExecutables associates CIM_Process to CIM_DataFile. So, to get only the CIM_DataFiles properties...you can do this:

      var wmiQueryString = string.Format( "associators of {{win32_process.Handle={0}}} where resultclass=cim_datafile", handle );
    

    ...which gets you directly to the CIM_DataFile properties...

    \\CLAYDEV\root\cimv2:CIM_DataFile
    AccessMask: 17957033
    Archive: True
    Caption: c:\windows\system32\winlogon.exe
    Compressed: False
    CompressionMethod:
    CreationClassName: CIM_LogicalFile
    CreationDate: 20170510121417.106825-240
    CSCreationClassName: Win32_ComputerSystem
    CSName: CLAYDEV
    Description: c:\windows\system32\winlogon.exe
    Drive: c:
    EightDotThreeFileName: c:\windows\system32\winlogon.exe
    Encrypted: False
    EncryptionMethod:
    Extension: exe
    FileName: winlogon
    FileSize: 707072
    FileType: Application
    FSCreationClassName: Win32_FileSystem
    FSName: NTFS
    Hidden: False
    InstallDate: 20170510121417.106825-240
    InUseCount:
    LastAccessed: 20170510121417.106825-240
    LastModified: 20170419020715.554583-240
    Manufacturer: Microsoft Corporation
    Name: c:\windows\system32\winlogon.exe
    Path: \windows\system32\
    Readable: True
    Status: OK
    System: False
    Version: 10.0.15063.250
    Writeable: True
    ...
    

    Getting only the interesting properties:

    I can't see where there's anyway to select a subset from an associators of or references of statement...but as suggested in the comments below, you can do a SelectMany to get just the properties you want:

      var wmiQueryString = string.Format( "associators of {{win32_process.Handle={0}}} where resultclass=cim_datafile", handle );
      using ( var searcher = new ManagementObjectSearcher( wmiQueryString ) )
      {
        var results =
          searcher
          .Get( )
          .OfType<ManagementBaseObject>( )
          .SelectMany
          ( df => df.Properties.OfType<PropertyData>( ).Where( pd => pd.Name == "Caption" ) );
    
        foreach ( PropertyData item in results )
        {
          Console.WriteLine( item.Value );
        }
      }
    

    The problem with that is it still iterates the entire property set to get the target property. It seems to run faster to just go straight at the property you want:

      var wmiQueryString = string.Format( "associators of {{win32_process.Handle={0}}} where resultclass=cim_datafile", handle );
      using ( var searcher = new ManagementObjectSearcher( wmiQueryString ) )
      using ( var results = searcher.Get( ) )
      {
        foreach ( ManagementObject item in results )
        {
          Console.WriteLine( item[ "Caption" ] );
        }
      }
    

    ...but it's not super fast either way, I'm afraid.