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

使用 Windows Management Instrumentation (WMI)——第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (7投票s)

2010年2月2日

CPOL

10分钟阅读

viewsIcon

34753

在本文的第二部分,我主要关注使用 WMI 和 C# 开发能够管理 Hyper-V 虚拟机的例程

引言

在本系列的第一部分中,我向您介绍了使用 C# 编写 WMI 客户端应用程序的基础知识。现在您已经很好地掌握了这项技术以及如何编写代码来使用 WMI 类,我们将进入一个稍微高级一点的主题。在这一部分中,我将带您了解基于 Windows Hyper-V 的服务器虚拟化,以及如何编写自定义代码以在您自己的应用程序中管理虚拟机。

背景

服务器虚拟化如今正在大多数数据中心中普及。VMware 和 Hyper-V 等产品已成为人们在谈论整合数据中心和提供易于部署和管理的可扩展基础设施时常用的参考。虽然部署和使用虚拟机非常容易,但当您考虑在多台物理服务器上部署数百个虚拟机时,它会变得复杂得多。这些技术通常附带一些昂贵的管理应用程序,可用于自动化虚拟机的预配、部署和生命周期管理。大多数供应商都提供某种 API,可用于构建您自己的自定义虚拟机管理客户端。在本文中,我将介绍用户如何构建一个自定义客户端应用程序,该应用程序可以在基于 Windows 的 Hyper-V 虚拟化平台上管理虚拟机。

Microsoft 通过 WMI 公开所有管理功能,因此我们可以轻松地在上一篇文章中学到的知识基础上构建,并为自定义 Hyper-V 管理客户端创建构建块。Microsoft 销售一款名为 System Center Virtual Machine Manager (SCVMM) 的产品。对于企业而言,它的价格非常高。因此,对于对成本敏感的组织来说,构建自己的 Hyper-V VM 自定义管理解决方案可能确实有意义。我将只向您展示如何构建实现可管理性的基本功能,您可以使用此功能并进一步开发一个完整的可管理性解决方案——您可以尝试扩展此处提出的想法。

Hyper-V 简介

Hyper-V 是 Microsoft 最新最出色的服务器虚拟化产品。在 Hyper-V 之前,Windows 曾附带一款名为 Virtual PC 的产品,它具有类似的功能。通过 Hyper-V,Microsoft 已进入主流服务器虚拟化市场,直接与 VMware 竞争。Hyper-V 随 Windows 2008(及更高版本)服务器版免费提供。但是,根据服务器版本的不同,单台服务器上实际可托管的虚拟机数量存在限制。标准版可托管 1 个虚拟机,企业版最多可托管 4 个虚拟机,数据中心版可托管无限数量的虚拟机(取决于硬件可用性)。

在本文中,我不会深入探讨 Hyper-V 技术本身。我假设读者对 Hyper-V 技术有相当好的理解,并且需要编写脚本或代码来自动化某些虚拟机管理活动。要开始配置虚拟机,必须首先从服务器管理器创建 Hyper-V VM 角色。启用此角色后,用户可以使用提供的虚拟机创建向导创建虚拟机。向导通常会询问虚拟机所在的路径以及虚拟机 VHD 文件(虚拟硬盘)的位置。创建虚拟机后,您就可以在虚拟机上安装操作系统了。Hyper-V 支持大量客户操作系统,您可以在 Hyper-V 虚拟机上安装任何这些操作系统作为客户机。虚拟机可以启动、挂起(暂停)或停止。挂起状态也称为保存状态。除了对虚拟机的这些操作之外,您还可以拍摄虚拟机的快照,还可以导入或导出虚拟机(此过程允许您在主机之间移动虚拟机)。本文主要关注如何使用 WMI 和 C# 编写代码,以快速更改虚拟机的状态,例如从停止/暂停状态更改为启动状态。

使用 C# 和 WMI 连接虚拟机管理例程

读者必须非常熟悉如何连接到 WMI 提供程序并查询公开的 WMI 类的各种属性。如果您不熟悉,我建议您阅读文章使用 Windows 管理规范 - 第 1 部分。在此示例中,我们将连接到 root\virtualization 命名空间,然后查询 Hyper-V VM 类的相关属性,并执行一些将更改 VM 状态的方法。

建议您创建一个名为 TestVM 的虚拟机,您将在该虚拟机上尝试本文讨论的所有管理操作,而不会危及任何其他虚拟机。我还假设这些操作将在远程系统上完成。首先使用远程系统的用户名和密码设置 ConnectionOptions 对象。

ConnectionOptions co = new ConnectionOptions();
co.Username = "administrator";
co.Password = "wmitest";

现在通过将范围设置为虚拟化命名空间 root\virtualization 来设置管理范围

ManagementScope scope = new ManagementScope(@"\\testnode\root\virtualization", co);
scope.Connect();

上面代码片段中的“@”符号用于指示编译器将反斜杠视为 string 中的有效符号而不是转义字符。这省去了我们对 string 中使用的反斜杠进行双重处理的麻烦。这样,我们现在就创建了一个新的管理范围对象。这会将我们连接到存在虚拟化相关类的特定命名空间。我们在这里的尝试是更改测试虚拟机 (TestVM) 的运行状态。虚拟机可以处于多种状态,例如启动中、已启动、保存中、已保存、已暂停、正在恢复等。我将向您展示如何将虚拟机的状态从已停止更改为已启用。其他操作将只是此操作的逻辑扩展。此时,您需要了解一些与管理虚拟机相关的 WMI 类。

Msvm_ComputerSystem 是您需要了解的第一个类。此类表示宿主计算机系统或虚拟计算机系统(虚拟机)。以下代码片段显示了此类定义的属性。此类还定义了一个方法——“RequestStateChange”,它对于管理虚拟机的各种状态非常有用。

如果您注意下面的定义,您会发现它不像传统的类定义。这是因为它是以托管对象格式(MOF)存在的。MOF 用于描述通用信息模型实现中的类。就本文而言,您可以将其视为包含该类公开的所有属性和事件描述的标准类定义。但是,这与开发 WMI 提供程序的人员更相关,因此我不会在本文中深入探讨此主题。我将在本系列文章的后续部分中介绍 WMI 提供程序开发。

[Dynamic,
Provider
("VmmsWmiInstanceAndMethodProvider")]class Msvm_ComputerSystem : CIM_ComputerSystem
{
  string   Caption;
  string   Description;
  string   ElementName;
  datetime InstallDate;
  uint16   OperationalStatus[];
  string   StatusDescriptions[];
  string   Status;
  uint16   HealthState = 5;
  uint16   EnabledState;
  string   OtherEnabledState;
  uint16   RequestedState;
  uint16   EnabledDefault = 2;
  datetime TimeOfLastStateChange;
  string   CreationClassName;
  string   Name = "GUID";
  string   PrimaryOwnerName;
  string   PrimaryOwnerContact;
  string   Roles[];
  string   NameFormat;
  string   OtherIdentifyingInfo[];
  string   IdentifyingDescriptions[];
  uint16   Dedicated[];
  string   OtherDedicatedDescriptions[];
  uint16   ResetCapability = 1;
  uint16   PowerManagementCapabilities[];
  uint64   OnTimeInMilliseconds;
  datetime TimeOfLastConfigurationChange;
  uint32   ProcessID;
  uint16   AssignedNumaNodeList[];
};

现在让我们获取“Msvm_ComputerSystem”类的一个实例,以便我们可以在之后执行“RequestStateChange”方法,最终更改 TestVM 的状态。我们需要获取一个表示 Msvm_ComputerSystem 实例的对象。为此,让我们定义一个帮助例程,它将始终查询命名空间以获取此类的所有属性和方法。该帮助例程最终将返回我们虚拟机的 Management 对象,我们可以使用它来执行更改 VM 状态的方法。下面的代码片段显示了调用以获取表示 Msvm_ComputerSystem 类实例的 ManagementObject

ManagementObject vm = GetTargetComputer("TestVM", scope);

public static ManagementObject GetTargetComputer
(string vmElementName, ManagementScope scope)
{
    string query = string.Format("select * from 
    Msvm_ComputerSystem Where ElementName = '{0}'", vmElementName);

    ManagementObjectSearcher searcher = new 
      ManagementObjectSearcher(scope, new ObjectQuery(query));

    ManagementObjectCollection computers = searcher.Get();

    ManagementObject computer = null;

    foreach (ManagementObject instance in computers)
    {
      computer = instance;
      break;
    }
 return computer;
}

如您在上面的代码片段中看到的,我们使用 WQL(WMI 查询语言)查询在相关命名空间中查询我们的类,然后将 ManagementObject 返回给调用函数。如下所示

 vm = Utility.GetTargetComputer(vmName, scope);

现在我们已经获得了代表我们虚拟机的 Msvm_ComputerSystem 实例的对象,我们可以继续实际执行“RequestStateChange”方法。但是在执行该方法之前,您需要将输入参数传递给该方法。这可以通过使用 ManagementObject 基类中定义的 GetMethodParameters 函数来完成。

ManagementBaseObject inParams = vm.GetMethodParameters("RequestStateChange");

现在,inParams 代表包含我们需要传递给 RequestStateChange 方法的输入参数的对象集合。下面的代码片段展示了我们如何加载输入参数并最终执行该方法。

inParams["RequestedState"] = Enabled;
ManagementBaseObject outParams = 
vm.InvokeMethod("RequestStateChange", inParams, null);

if ((UInt32)outParams["ReturnValue"] == ReturnCode.Completed)
   {
     Console.WriteLine("{0} state was 
           changed successfully.", vmName);
    }
    else
    {
      Console.WriteLine("Change virtual system 
      state failed with error{0}", outParams["ReturnValue"]);
    } 

这样,我们就成功地更改了测试虚拟机 (TestVM) 的状态。您也可以尝试更改为虚拟机支持的其他状态。

现在我们已经成功更改了虚拟机的状态,我们应该关注处理状态更改的一些更精细的点。在上面的代码片段中,我请求将状态更改为“Enabled”而没有检查它是否已经处于该状态。一个好的做法是检查现有状态,然后如果它不处于该状态,则执行 RequestStateChange 方法。然而,这可以是我在本文第一部分中展示的简单扩展。因此,我鼓励读者尝试一下。我稍后可以作为本文的评论发布该代码片段。

此外,在虚拟机上执行状态更改时,我们应该注意,调用可能无法立即返回成功,因为在虚拟机正在启动的情况下,它还无法返回已启动状态。因此,我们还应该处理一种机制,以等待状态更改并报告正确的状态。下面的代码片段展示了我们如何处理上述两种情况,以使我们的应用程序更可靠和完整。为此,我定义了一个实用函数,该函数获取提供程序返回的 Job 事件的状态——此函数称为“JobCompleted()”。然后我在主代码中使用 JobCompleted 函数来跟踪 InvokeMethod 结果的完成。代码片段的更多详细信息将在下面介绍

if ((UInt32)outParams["ReturnValue"] 
          == ReturnCode.Started)
    {
     if (Utility.JobCompleted(outParams, scope))
      {
        Console.WriteLine("{0} state was changed 
        successfully.",   vmName);
      }
      else
      {
        Console.WriteLine("Failed to change 
            virtual system state");
      }
     }
    else if ((UInt32)outParams["ReturnValue"] 
           == ReturnCode.Completed)
     {
        Console.WriteLine("{0} state was 
            changed successfully.", vmName);
     }
     else
     {
      Console.WriteLine("Change virtual 
        system state failed with error {0}", 
        outParams ["ReturnValue"]);
     }

如您所注意到的,在上面的代码片段中,我调用了 JobCompleted 函数来等待“RequestStateChange()”方法的实际完成。JobCompleted 函数的描述如下

//This code demonstrates how the VM state change can be tracked

public static bool JobCompleted(ManagementBaseObject outParams, 
                                ManagementScope scope)
{
    bool jobCompleted = true;

    //Retrieve msvc_StorageJob path. This is a full wmi path
    string JobPath = (string)outParams["Job"];
    ManagementObject Job = new ManagementObject(scope,
    new ManagementPath(JobPath), null);

    //Try to get storage job information
    Job.Get();
    while ((UInt16)Job["JobState"] == JobState.Starting
        || (UInt16)Job["JobState"] == JobState.Running)
    {
        Console.WriteLine("In progress... {0}% completed.", 
            Job["PercentComplete"]);
        System.Threading.Thread.Sleep(1000);
        Job.Get();
    }

    //Figure out if job failed
    UInt16 jobState = (UInt16)Job["JobState"];
    if (jobState != JobState.Completed)
    {
        UInt16 jobErrorCode = (UInt16)Job["ErrorCode"];
        Console.WriteLine("Error Code:{0}", jobErrorCode);
        Console.WriteLine("ErrorDescription: {0}", 
         (string)Job["ErrorDescription"]);
        jobCompleted = false;
    }
    return jobCompleted;
}

虽然上面看起来像跟踪函数调用进度的大量代码,但它实际上非常简单。WMI 允许异步跟踪方法的完成。例如,在我们上面的示例中,系统不可能立即返回已完成状态。这是因为启用或启动 VM 可能需要时间。因此,在这种情况下,可以获取“Job”对象的路径,这基本上用于获取已执行方法的状​​态。在这种情况下,我们从方法调用时返回的 outParams 中获取“Job”对象的路径。与往常一样,对象的路径用于初始化新的 ManagementObject,然后用于不断轮询当前状态。Job.Get() 返回当前状态,然后将其与预定义的 JobState 常量(如 StartingRunning 等)进行比较。如果您注意到,我们在 while 循环中设置了一个小的睡眠,以避免过多的轮询。我们最后检查 Job 是否失败或成功,然后适当地退出并向调用者返回一个布尔值。

至此,我完成了本文关于如何使用 WMI 和 C# 管理虚拟机的部分。希望您能利用这里提供的想法,在您自己的应用程序中扩展和添加更复杂的功能,以管理 Hyper-V 虚拟机。

参考文献

  • MSDN 在线 API 参考 WMI SDK 示例和文档

历史

  • 2010 年 2 月 1 日:首次发布
© . All rights reserved.