65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (4投票s)

2010年2月23日

CPOL

7分钟阅读

viewsIcon

35113

本文将深入探讨一些实用的工具函数,它们可以帮助您构建强大的基于 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 日:首次发布
© . All rights reserved.