3

tl;dr What should I SELECT instead of * in order to get the methods?

More info:

Here's an example:

using (var s = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM someClass"))
    foreach (var obj in s.Get())

If I just ask for one property it's not enough - I get an exception when trying obj.InvokeMethod(...);.

If I ask for * it is enough but I rather avoid this if possible.

I don't see any property for getting the methods (-Disable, Enable,...) in the docs for WMI classes. And if it's not on the list - how come * works? Isn't it just supposed to ask for all of those on the list?

EDIT

Someone suggested using ManagementClass instead of ManagementObjectSearcher. Does this load all properties like *? (If not, that's a good answer. Though in my actual case I need a property besides the ability to call a method. And my theoretical questions remains - is * more than just all.)

Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
ispiro
  • 26,556
  • 38
  • 136
  • 291
  • WMI is pretty rough with finding what you need but this tool was really helpful for me to learn how it works. https://www.microsoft.com/en-us/download/details.aspx?id=8572. – Sam Marion Apr 12 '18 at 14:17
  • It looks like they took it down... I still have it if you want I can host it and provide a link to download. – Sam Marion Apr 12 '18 at 14:18
  • @SamMarion Thanks. I already have the tool somewhere. (At least I think I do) but my question isn't just how to get the methods. My question is how to get them _without_ using `*`. – ispiro Apr 12 '18 at 14:20
  • The tool generates code that doesn't use the star and will show you what methods are available if I remember correctly. Does it not? – Sam Marion Apr 12 '18 at 14:23
  • Nevermind, I seem to be remembering the tool provided from Microsoft uses the * in all their queries, sorry. – Sam Marion Apr 12 '18 at 14:29
  • @SamMarion I just tried it, and under the "Execute a Method" tab it returns a script. It's very different from code, and seems to load the whole class. – ispiro Apr 12 '18 at 14:30
  • have you looked here? https://msdn.microsoft.com/en-us/library/f9ck6sf2(v=vs.110).aspx – styx Apr 12 '18 at 14:39
  • @styx Thanks. Perhaps I'm wrong about this, but doesn't that load the whole class like using `*`? – ispiro Apr 12 '18 at 14:47
  • @ispiro, seems like it does – styx Apr 12 '18 at 14:50

1 Answers1

2

As far as what the title and TL;DR are asking, methods cannot be queried via SELECT but you can use the ManagementClass.Methods property to inspect those provided by that management class. For example, this code...

using (ManagementClass processClass = new ManagementClass("Win32_Process"))
    foreach (MethodData method in processClass.Methods)
    {
        bool isStatic = method.Qualifiers
            .Cast<QualifierData>()
            .Any(qualifier => string.Equals(qualifier.Name, "Static", StringComparison.OrdinalIgnoreCase));

        Console.WriteLine($"{method.Origin}.{method.Name}() [{(isStatic ? "static" : "instance")}]");
        if (method.InParameters != null && method.InParameters.Properties.Count > 0)
        {
            Console.WriteLine("\tInput parameters:");
            foreach (PropertyData parameterProperty in method.InParameters.Properties)
                Console.WriteLine($"\t\t{parameterProperty.Type} {parameterProperty.Name}");
        }
        if (method.OutParameters != null && method.OutParameters.Properties.Count > 0)
        {
            Console.WriteLine("\tOutput parameters:");
            foreach (PropertyData parameterProperty in method.OutParameters.Properties)
                Console.WriteLine($"\t\t{parameterProperty.Type} {parameterProperty.Name}");
        }
    }

...produces this output...

Win32_Process.Create() [static]
    Input parameters:
        String CommandLine
        String CurrentDirectory
        Object ProcessStartupInformation
    Output parameters:
        UInt32 ProcessId
        UInt32 ReturnValue
Win32_Process.Terminate() [instance]
    Input parameters:
        UInt32 Reason
    Output parameters:
        UInt32 ReturnValue
Win32_Process.GetOwner() [instance]
    Output parameters:
        String Domain
        UInt32 ReturnValue
        String User
...

Unless "get methods" has a different meaning than I think it does (distinct from calling methods), the rest of the question seems to be dealing with something else entirely, which is the necessity of populating Key properties before invoking a method. I believe this is addressed in another question of yours, Why does WMI work through a search but not directly?

If what you're really asking is "How can I determine the minimum set of properties I need to select in order to invoke a method?", then you can accomplish that with ManagementClass, too. That minimum set of properties are all properties with a Key qualifier, so you would use the Properties property to find any property whose Qualifiers property contains a qualifier with a Name of "Key".

Consider the Win32_Product class, which represents products installed by Windows Installer and (as determined on my Windows 10 system, differing from the documentation) has these Key properties...

  • IdentifyingNumber
  • Name
  • Version

Let's say you want to retrieve these properties to display...

  • PackageName
  • Vendor
  • Version

...and then call the Uninstall() method for a product with a Name of "Microsoft .NET Framework 4.8 SDK". The following code shows three different ways to attempt this task...

  1. Select all properties and invoke the method.
  2. Select only the properties you want to display and invoke the method.
  3. Select the properties you want to display plus the Key properties and invoke the method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;

namespace SO49798851
{
    static class Program
    {
        static void Main()
        {
            // Win32_Product is slow to query, so be patient!
            const string className = "Win32_Product";
            const string condition = "Name = 'Microsoft .NET Framework 4.8 SDK'";
            string[] allProperties = new string[] { "*" };
            string[] displayProperties = new string[] { "PackageName", "Vendor", "Version" };
            string[] keyProperties = GetKeyPropertyNames(className);
            string[] displayPlusKeyProperties = displayProperties.Union(keyProperties).ToArray();
            // When run as a non-administrator, the Uninstall() method
            // still returns 0 despite not (appearing to) do anything
            const string methodName = "Uninstall";
            object[] methodArguments = Array.Empty<object>();

            Console.WriteLine($"Key properties for class {className}: {string.Join(", ", keyProperties)}");
            Console.WriteLine();

            FindAndInvoke(className, condition, allProperties,            methodName, methodArguments);
            FindAndInvoke(className, condition, displayProperties,        methodName, methodArguments);
            FindAndInvoke(className, condition, displayPlusKeyProperties, methodName, methodArguments);
        }

        static string[] GetKeyPropertyNames(string className)
        {
            using (ManagementClass managementClass = new ManagementClass(className))
            {
                return managementClass.Properties
                    .Cast<PropertyData>()
                    .Where(
                        property => property.Qualifiers
                            .Cast<QualifierData>()
                            .Any(qualifier => string.Equals(qualifier.Name, "Key", StringComparison.OrdinalIgnoreCase))
                    )
                    .Select(property => property.Name)
                    .ToArray();
            }
        }

        static void FindAndInvoke(
            string className,
            string condition,
            string[] selectedProperties,
            string methodName,
            object[] methodArguments
        )
        {
            if (selectedProperties == null)
                selectedProperties = Array.Empty<string>();

            ObjectQuery query = new SelectQuery(className, condition, selectedProperties);
            bool allPropertiesSelected = selectedProperties.Length < 1
                || selectedProperties.Any(propertyName => propertyName == "*");

            Console.WriteLine(query.QueryString);
            Console.WriteLine(new string('=', query.QueryString.Length));

            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
            using (ManagementObjectCollection searchResultCollection = searcher.Get())
            {
                // ManagementObjectCollection doesn't support indexing; this is the
                // least-ugly, least-verbose way to enumerate it with an index variable
                ManagementObject[] searchResultArray = searchResultCollection.Cast<ManagementObject>().ToArray();

                for (int i = 0; i < searchResultArray.Length; i++)
                    using (ManagementObject searchResult = searchResultArray[i])
                    {
                        Console.WriteLine($"{className}[{i}].Path.RelativePath: {searchResult.Path.RelativePath}");
                        Console.WriteLine($"{className}[{i}].Properties.Count: {searchResult.Properties.Count}");
                        foreach (PropertyData property in searchResult.Properties)
                            if (allPropertiesSelected
                                    || selectedProperties.Contains(property.Name, StringComparer.OrdinalIgnoreCase)
                            )
                            {
                                object displayValue = property.Value ?? "<null>";

                                Console.WriteLine($"{className}[{i}].Properties[\"{property.Name}\"]: {displayValue}");
                            }

                        try
                        {
                            object methodResult = searchResult.InvokeMethod(methodName, methodArguments);

                            Console.WriteLine($"{className}[{i}].{methodName}() completed with result {methodResult}.");
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"{className}[{i}].{methodName}() failed with {ex}.");
                        }
                        Console.WriteLine();
                    }
            }
        }
    }
}

...and produces this output...

Key properties for class Win32_Product: IdentifyingNumber, Name, Version

select * from Win32_Product where Name = 'Microsoft .NET Framework 4.8 SDK'
===========================================================================
Win32_Product[0].Path.RelativePath: Win32_Product.IdentifyingNumber="{949C0535-171C-480F-9CF4-D25C9E60FE88}",Name="Microsoft .NET Framework 4.8 SDK",Version="4.8.03928"
Win32_Product[0].Properties.Count: 27
Win32_Product[0].Properties["AssignmentType"]: 1
Win32_Product[0].Properties["Caption"]: Microsoft .NET Framework 4.8 SDK
Win32_Product[0].Properties["Description"]: Microsoft .NET Framework 4.8 SDK
Win32_Product[0].Properties["HelpLink"]: <null>
Win32_Product[0].Properties["HelpTelephone"]: <null>
Win32_Product[0].Properties["IdentifyingNumber"]: {949C0535-171C-480F-9CF4-D25C9E60FE88}
Win32_Product[0].Properties["InstallDate"]: 20191001
Win32_Product[0].Properties["InstallDate2"]: <null>
Win32_Product[0].Properties["InstallLocation"]: <null>
Win32_Product[0].Properties["InstallSource"]: C:\ProgramData\Microsoft\VisualStudio\Packages\Microsoft.Net.4.8.SDK,version=4.8.3928.1\
Win32_Product[0].Properties["InstallState"]: 5
Win32_Product[0].Properties["Language"]: 1033
Win32_Product[0].Properties["LocalPackage"]: C:\WINDOWS\Installer\34d24bd7.msi
Win32_Product[0].Properties["Name"]: Microsoft .NET Framework 4.8 SDK
Win32_Product[0].Properties["PackageCache"]: C:\WINDOWS\Installer\34d24bd7.msi
Win32_Product[0].Properties["PackageCode"]: {CC6C9CC4-DDCD-4C14-81E1-4007EE49D7C0}
Win32_Product[0].Properties["PackageName"]: sdk_tools48.msi
Win32_Product[0].Properties["ProductID"]: <null>
Win32_Product[0].Properties["RegCompany"]: <null>
Win32_Product[0].Properties["RegOwner"]: <null>
Win32_Product[0].Properties["SKUNumber"]: <null>
Win32_Product[0].Properties["Transforms"]: <null>
Win32_Product[0].Properties["URLInfoAbout"]: <null>
Win32_Product[0].Properties["URLUpdateInfo"]: <null>
Win32_Product[0].Properties["Vendor"]: Microsoft Corporation
Win32_Product[0].Properties["Version"]: 4.8.03928
Win32_Product[0].Properties["WordCount"]: 0
Win32_Product[0].Uninstall() completed with result 0.

select PackageName,Vendor,Version from Win32_Product where Name = 'Microsoft .NET Framework 4.8 SDK'
====================================================================================================
Win32_Product[0].Path.RelativePath: 
Win32_Product[0].Properties.Count: 3
Win32_Product[0].Properties["PackageName"]: sdk_tools48.msi
Win32_Product[0].Properties["Vendor"]: Microsoft Corporation
Win32_Product[0].Properties["Version"]: 4.8.03928
Win32_Product[0].Uninstall() failed with System.InvalidOperationException: Operation is not valid due to the current state of the object.
   at System.Management.ManagementObject.InvokeMethod(String methodName, Object[] args)
   at SO49798851.Program.FindAndInvoke(String className, String condition, String[] selectedProperties, String methodName, Object[] methodArguments) in ...\Program.cs:line 90.

select PackageName,Vendor,Version,IdentifyingNumber,Name from Win32_Product where Name = 'Microsoft .NET Framework 4.8 SDK'
===========================================================================================================================
Win32_Product[0].Path.RelativePath: Win32_Product.IdentifyingNumber="{949C0535-171C-480F-9CF4-D25C9E60FE88}",Name="Microsoft .NET Framework 4.8 SDK",Version="4.8.03928"
Win32_Product[0].Properties.Count: 5
Win32_Product[0].Properties["IdentifyingNumber"]: {949C0535-171C-480F-9CF4-D25C9E60FE88}
Win32_Product[0].Properties["Name"]: Microsoft .NET Framework 4.8 SDK
Win32_Product[0].Properties["PackageName"]: sdk_tools48.msi
Win32_Product[0].Properties["Vendor"]: Microsoft Corporation
Win32_Product[0].Properties["Version"]: 4.8.03928
Win32_Product[0].Uninstall() completed with result 0.

Here are the takeaways from those three attempts...

  1. Selecting all properties, of course, results in a successful method invocation, but we end up with way more properties than we wanted.
  2. Selecting exactly the properties we want - but not all Key properties - results in a failed method invocation.
    • Notice that even though one of the Key properties, Version, was part of our query, the searchResult.Path property is empty in this case, a sure sign that something isn't quite right with our result object.
  3. Selecting exactly the properties we want plus the Key properties results in a successful method invocation with no extraneous properties.
    • If we have a collection of properties we want (i.e. displayProperties) and a collection of Key properties (i.e. keyProperties), the Union() LINQ method makes it easy to combine them to get the minimum properties to select.
Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68