在.NET框架中,Windows Management Instrumentation(WMI)提供了一种机制,允许开发者访问和操作Windows操作系统的管理信息。WMI对象在.NET框架中通过System.Management命名空间进行访问。但是,直接访问WMI对象的成员并不直观,需要通过特定的方法来实现。
例如,在C#中,要访问WMI对象的属性,不能直接使用点操作符(.),而必须使用ManagementObject.Properties["PropertyName"]或者ManagementObject["PropertyName"]来获取一个PropertyData对象,然后通过PropertyData.Value来获取属性值。
ManagementObject instance;
PropertyData data = instance["PropertyName"];
Console.WriteLine(data.Value);
PropertyData对象还提供了其他属性,如Type、IsLocal、IsArray和Qualifiers,这些属性可以让获取关于PropertyData对象的额外信息。Qualifiers属性返回一个QualifierDataCollection,这是一个包含该属性所有WMI限定符的集合。
以下是一个使用System.Management命名空间获取Win32_Process信息的示例:
using System;
using System.Management;
class Program {
static void Main() {
string className = "Win32_Process";
ManagementClass WMIClass = new ManagementClass(className);
ManagementObjectCollection instances = WMIClass.GetInstances();
foreach (ManagementObject instance in instances) {
Console.WriteLine("Process Name:\t " + instance["Name"]);
Console.WriteLine("Command Line:\t " + instance["CommandLine"] + "\n");
}
}
}
虽然这种方法可行,但直接访问对象成员会更简单。为了实现这一点,可以使用.NETFramework SDK附带的一个命令行工具:Management Strongly Typed Class Generator(mgmtclassgen.exe)。这个工具的基本语法是:
mgmtclassgen <WMI class name> /P output file path
例如:
mgmtclassgen Win32_Process /P C:\Win32_Process.cs
执行这个命令后,会创建一个名为Win32_Process.cs的C#源文件。这个文件可以被包含在Visual Studio项目中。打开Win32_Process.cs文件,会看到一段很长的代码,它定义了一个Process类,这个类封装了Win32_Process类。
使用mgmtclassgen.exe生成的类,可以更简洁地访问WMI对象的属性:
using System;
using System.Management;
using ROOT.CIMV2.Win32;
class Program {
static void Main() {
foreach (Process win32Process in Process.GetInstances()) {
Console.WriteLine(win32Process.Name);
Console.WriteLine(win32Process.CommandLine + "\n");
}
}
}
这种方式比之前的示例更简洁,语法更清晰,并且支持Intellisense功能。
调用WMI类的方法并不直观,需要使用间接方法调用。首先需要使用ManagementClass.GetMethodParameters()获取方法输入参数信息,为每个参数赋值,然后将其与方法名一起传递给ManagementClass.InvokeMethod()。InvokeMethod()返回一个ManagementBaseObject,它代表输出参数,也需要间接访问它。
以下是一个调用Win32_Process类Create方法的示例:
ManagementBaseObject inParams = null;
ManagementClass classObj = new ManagementClass(null, "Win32_Process", null);
inParams = classObj.GetMethodParameters("Create");
inParams["CommandLine"] = "Notepad.exe";
ManagementBaseObject outParams = classObj.InvokeMethod("Create", inParams, null);
Console.WriteLine(outParams.Properties["ProcessId"].Value);
与使用mgmtclassgen.exe生成的Process类相比,只需要一行代码就可以调用方法:
using System;
using System.Management;
using ROOT.CIMV2.Win32;
class Program {
static void Main() {
uint handle;
Process.Create("Notepad.Exe", null, null, out handle);
Console.WriteLine(handle.ToString());
}
}
如果查看生成的类代码中Process.Create()的定义,会发现它在幕后执行了与第一段代码相同的间接方法调用。只是这种情况下,复杂性被隐藏在了一个单独的类中。
大多数情况下,mgmtclassgen.exe工作得很好,但如果要使用的类来自Root\Cimv2 WMI命名空间之外,并且包含静态方法,如StdRegProv WMI类(用于访问注册表),就会出现问题。
mgmtclassgen.exe用于为StdRegProvWMI类生成一个强类型类。但是,如果不报告任何错误,上述简单的代码列出HKLM\Software子键会返回一个异常:"未找到"。如果查看StdRegProv.cs代码以找到其EnumKey()方法,会发现(经过一些重新格式化):
public static uint EnumKey(uint hDefKey, string sSubKeyName, out string[] sNames) {
bool IsMethodStatic = true;
if ((IsMethodStatic == true)) {
System.Management.ManagementBaseObject inParams = null;
System.Management.ManagementPath mgmtPath = new System.Management.ManagementPath(CreatedClassName);
System.Management.ManagementClass classObj = new System.Management.ManagementClass(statMgmtScope, mgmtPath, null);
inParams = classObj.GetMethodParameters("EnumKey");
inParams["hDefKey"] = ((System.UInt32)(hDefKey));
inParams["sSubKeyName"] = ((System.String)(sSubKeyName));
System.Management.ManagementBaseObject outParams = classObj.InvokeMethod("EnumKey", inParams, null);
sNames = ((string[])(outParams.Properties["sNames"].Value));
return System.Convert.ToUInt32(outParams.Properties["ReturnValue"].Value);
} else {
sNames = null;
return System.Convert.ToUInt32(0);
}
}
ManagementClass构造函数接受statMsgScope参数,它应该持有指向Root\Default WMI命名空间的ManagementScope的引用,StdRegProv类位于该命名空间。但是,在StdRegProv.cs的开头并没有这样做:
private static System.Management.ManagementScope statMgmtScope = null;
有了这个声明,statMgmtScope默认为Root\Cimv2 - 难怪方法调用抛出"未找到"异常 - Root\Cimv2命名空间中没有StdRegrov类。因此,最后要使其工作,需要将上述声明更改为:
private static System.Management.ManagementScope statMgmtScope = new ManagementScope("root\\default");