使用 Windows Management Instrumentation (WMI) - 第四部分





4.00/5 (4投票s)
本文将深入探讨一些实用的工具函数,它们可以帮助您构建强大的基于 WMI 的应用程序。
引言
在本文中,我主要关注一个有用的实用程序,该实用程序将帮助开发人员完成多项管理任务,从而使他们基于 WMI 的应用程序更加健壮和可扩展。在本系列的上一篇文章中,我讨论了如何编写多个 VM 管理例程并将它们集成到我们的自定义应用程序中。但是,任何应用程序如果没有复制文件、目录、删除文件、创建和执行进程等管理工作,都是不完整的。在本文中,我将重点介绍其中一个实用程序,并讨论为什么使用 WMI 来执行其中一些任务而不是纯粹的 Win32 调用会很有用。一旦您理解了这个思路,就可以通用地扩展此方法并在自己的应用程序中使用它。
背景
WMI 是管理数据中心中多个系统的强大方法。WMI 在子系统的特定性之上提供了一个抽象层,同时为开发人员提供了一个通用的框架来编写强大的可管理性解决方案。到目前为止,我们已经讨论了如何在 C# 应用程序中实例化和使用 WMI 类和方法。我还专门讨论了 Hyper-V 管理方法和类的工作原理。为了集成一些强大的功能,我认为讨论一些我在应用程序中广泛使用的实用程序是合适的。我将向您介绍一个实用程序,它可以帮助您在远程系统上创建进程并实例化 Rocbocopy 或普通复制等操作,同时我还会向您展示如何使用“System.Management
”类来等待远程进程完成。
创建远程进程
我认为大多数人都熟悉在 Windows 中创建进程的方式。然而,我只想回顾一下进程的概念,然后讨论 WMI 的实现方式。正如大多数人所知,Windows 通过 Win32 编程 API 暴露了几个重要的 API。这构成了许多应用程序的骨干。人们通常会围绕 Win32 API 编写自己的包装器,并在其之上构建复杂的功能。假设您想创建一个进程并执行一个名为“foo.exe”的程序。因此,在传统的 Win32 方法中,您将使用 CreateProcess()
API,并将进程名称“foo.exe”传递给该 API。虽然这种方法在大多数情况下都能正常工作,但可能需要从远程计算机上的远程计算机启动 CreateProcess()
,并使用远程计算机上的二进制文件。这就是使用 WMI 发挥巨大作用的地方。
实例化远程进程 - Win32_Process 类
本文将使用的类是 Win32_Process
类。Win32_Process
类包含用于进程操作和创建的所有属性和方法。下面的代码片段显示了该类支持的属性和方法。
属性
//Attributes of the Win32_class
class Win32_Process : CIM_Process
{
string Caption;
string CommandLine;
string CreationClassName;
datetime CreationDate;
string CSCreationClassName;
string CSName;
string Description;
string ExecutablePath;
uint16 ExecutionState;
string Handle;
uint32 HandleCount;
datetime InstallDate;
uint64 KernelModeTime;
uint32 MaximumWorkingSetSize;
uint32 MinimumWorkingSetSize;
string Name;
string OSCreationClassName;
string OSName;
uint64 OtherOperationCount;
uint64 OtherTransferCount;
uint32 PageFaults;
uint32 PageFileUsage;
uint32 ParentProcessId;
uint32 PeakPageFileUsage;
uint64 PeakVirtualSize;
uint32 PeakWorkingSetSize;
uint32 Priority;
uint64 PrivatePageCount;
uint32 ProcessId;
uint32 QuotaNonPagedPoolUsage;
uint32 QuotaPagedPoolUsage;
uint32 QuotaPeakNonPagedPoolUsage;
uint32 QuotaPeakPagedPoolUsage;
uint64 ReadOperationCount;
uint64 ReadTransferCount;
uint32 SessionId;
string Status;
datetime TerminationDate;
uint32 ThreadCount;
uint64 UserModeTime;
uint64 VirtualSize;
string WindowsVersion;
uint64 WorkingSetSize;
uint64 WriteOperationCount;
uint64 WriteTransferCount;
};
方法
AttachDebugger
- 启动当前注册的进程调试器。Create
- 创建一个新进程。GetOwner
- 检索进程正在运行的用户和域名称。GetOwnerSid
- 检索进程所有者的安全标识符 (SID)。SetPriority
- 更改进程的执行优先级。Terminate
- 终止进程及其所有线程。
在上面的方法列表中,我们感兴趣的方法是“Create
”方法。Create
方法允许我们创建进程,这类似于 Win32
API 的“CreateProcess()
”调用。 “Create
”方法接受两个输入参数。一个是进程的命令行,另一个是必须从中执行的当前目录。
public static UInt32 RemoteCopy(String Node, String UserName,
String Password, String VMname, String src, String dst, Boolean local)
{
if (local.Equals(false))
{
ManagementClass process = new ManagementClass("Win32_Process");
ConnectionOptions co1 = new ConnectionOptions();
co1.Username = UserName;
co1.Password = Password;
ManagementPath mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");
process.Scope = new ManagementScope(mp1, co1);
process.Scope.Connect();
ManagementBaseObject inparams = process.GetMethodParameters("Create");
inparams["CommandLine"] = @"cmd.exe /c copy " + @src + " " + @dst;
ManagementBaseObject outparams =
process.InvokeMethod("Create", inparams, null);
String ProcID = outparams["ProcessID"].ToString();
String retval = outparams["ReturnValue"].ToString();
}
在上面的代码片段中,我传递了节点名称、用户名、密码和一个布尔值 local,如果设置为 true
表示函数将在本地执行,如果设置为 false
表示它是远程节点。我还传递了复制操作必须在其中工作的源文件夹和目标文件夹。有许多更有效的方法可以确定节点是本地还是远程,但现在我们将坚持使用传递显式布尔值的这种方法。
像往常一样,我们设置连接对象,然后连接到我们的命名空间 root\cimv2。我们使用此命名空间,因为它包含所有系统管理类。由于我们已连接到 Win32_Process
,现在可以获取“Create
”方法所需的参数。“Create
”方法接受命令行参数和当前目录。由于我们在命令行参数本身中传递了完整路径,因此当前目录在这里不太重要。一旦我们调用该方法,我们就会像往常一样在“outparams
”数组中获得输出参数。我们从输出中获得两个参数——一个是已创建进程的 ProcessID
,以及 Create
本身的任何返回值——例如,如果 Create
失败并返回了错误。
此时,操作仅部分完成。您可能已经注意到,我们已经启动了一个复制操作。只要此方法没有后续依赖项,您就没事了,但是大多数情况下,接下来的代码行都依赖于复制操作的完成。因此,我们必须等待该进程完成。在下面的子部分中,我将向您展示如何编写一些简单的代码,让我们的代码等待进程完成,以便我们可以继续进行其他依赖函数。
等待远程进程完成
在本节中,我们将改进我们之前的函数,使其更加完整。任何在本地系统上使用过 CreateProcess
的人都知道,大多数时候您需要等待该进程完成。然而,当您在本地执行此操作时,有几种方法可以使用同步原语(如 WaitForSingleObject
)来管理此操作。
以下代码片段显示了我们可以添加到上述方法中的附加代码行,以便在返回之前等待进程完成。
string pol = "2";
string queryString =
"SELECT *" +
" FROM __InstanceOperationEvent " +
"WITHIN " + pol +
" WHERE TargetInstance ISA 'Win32_Process' " +
" AND TargetInstance.Name = '" + "robocopy.exe" + "'";
WqlEventQuery wql = new WqlEventQuery(queryString);
// You could replace the dot by a machine name to watch to that machine
//string scope = @"\\" + Node + @"\" + @"root\cimv2";
mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");
co1 = new ConnectionOptions();
co1.Username = UserName;
co1.Password = Password;
ManagementScope Scope = new ManagementScope(mp1, co1);
Scope.Connect();
// create the watcher and start to listen
ManagementEventWatcher watcher = new ManagementEventWatcher();
watcher.Query = wql;
watcher.Scope = Scope;
int i = 1;
while (i == 1)
{
ManagementBaseObject MBOobj = watcher.WaitForNextEvent();
if ((((ManagementBaseObject)MBOobj["TargetInstance"])
["ProcessID"].ToString() == ProcID))
{
i = 2; //just exit otherwise it will block
Console.WriteLine("Robocop Exit event arrived");
}
}
watcher.Stop();
因此,在上面的代码片段中,我们初始化了一个 WQL 查询(WMI 查询语言)——请记住 WQL 的语法类似于 SQL,并允许您编写嵌套查询来缩小您希望从 CIM 命名空间获取的对象。我们初始化了一个 WQL 查询事件,它将被用来通知我们。这会获取我们创建的查询,并返回一个 WqlEventQuery
类型的对象。然后,我们初始化一个 ManagementEvenWatcher
,它实际上会监视 WqlEventQuery
对象指定的事件的发生。我们将 WqlEventQuery
对象“Wq1
”和我们对象的“Scope
”传递给“Watcher
”对象。现在,我们创建一个无限循环来轮询我们感兴趣的事件。“watcher.WaitForNextEevent
”将跟踪所有返回的事件。一旦我们将返回的事件与我们的 ProcessID
进行比较,我们就会立即退出循环。正如您所见,如果进程因某种原因挂起,可能会进入无限循环。我相信您可以对其进行修改,以避免潜在的挂起问题(例如超时)。
现在将这两段代码放在一起,我们得到了下面的函数,它具备了远程创建进程并等待其完成的能力。
public static UInt32 RemoteCopy(String Node, String UserName,
String Password, String VMname, String src, String dst, Boolean local)
{
if (local.Equals(false))
{
ManagementClass process = new ManagementClass("Win32_Process");
ConnectionOptions co1 = new ConnectionOptions();
co1.Username = UserName;
co1.Password = Password;
ManagementPath mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");
process.Scope = new ManagementScope(mp1, co1);
process.Scope.Connect();
ManagementBaseObject inparams = process.GetMethodParameters("Create");
inparams["CommandLine"] = @"cmd.exe /c Robocopy " + @src + " " + @dst;
ManagementBaseObject outparams =
process.InvokeMethod("Create", inparams, null);
String ProcID = outparams["ProcessID"].ToString();
String retval = outparams["ReturnValue"].ToString();
string pol = "2";
string queryString =
"SELECT *" +
" FROM __InstanceOperationEvent " +
"WITHIN " + pol +
" WHERE TargetInstance ISA 'Win32_Process' " +
" AND TargetInstance.Name = '" + "robocopy.exe" + "'";
WqlEventQuery wql = new WqlEventQuery(queryString);
mp1 = new ManagementPath(@"\\" + Node + @"\" + @"root\cimv2");
co1 = new ConnectionOptions();
co1.Username = UserName;
co1.Password = Password;
ManagementScope Scope = new ManagementScope(mp1, co1);
Scope.Connect();
// create the watcher and start to listen
ManagementEventWatcher watcher = new ManagementEventWatcher();
watcher.Query = wql;
watcher.Scope = Scope;
int i = 1;
while (i == 1)
{
ManagementBaseObject MBOobj = watcher.WaitForNextEvent();
if ((((ManagementBaseObject)MBOobj
["TargetInstance"])["ProcessID"].ToString() == ProcID))
{
i = 2; //just exit otherwise it will block
Console.WriteLine("Robocop Exit event arrived");
}
}
watcher.Stop();
return ((UInt32)0);//return success
}
至此,我完成了可以帮助您开发更健壮的基于 WMI 的应用程序的实用程序函数的一部分。在后续的部分中,我可能会尝试涵盖不同的方法和实用程序,甚至可能尝试使用不同的编程语言,如 C++(它仍然是开发人员中非常流行的语言)。
参考
- MSDN 在线 API 参考 WMI SDK 示例和文档
历史
- 2010 年 2 月 23 日:首次发布