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

程序化 Hyper-V 管理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (7投票s)

2010年5月27日

CPOL

15分钟阅读

viewsIcon

65730

downloadIcon

1492

在本文中,我们将探讨如何使用 C++ 语言和 WMI 技术对 Microsoft Hyper-V 虚拟机监控程序进行程序化控制。

目录

  1. 引言
    1. 虚拟机监控程序的概念。现有产品简述
    2. Microsoft Hyper-V 虚拟机监控程序
  2. Hyper-V 虚拟机监控程序的程序化控制
    1. WMI 技术
    2. 用于 Hyper-V 控制的主要 WMI 对象描述
    3. 连接到虚拟机监控程序
    4. 获取虚拟机列表
    5. 获取虚拟机属性
    6. 启动、停止和检查虚拟机状态
  3. 结论
  4. 有用链接
  5. 附件 - 示例源代码,说明
  6. 历史

1. 引言

1.1. 虚拟机监控程序的概念。现有产品简述

虚拟机监控程序,也称为虚拟机监视器 (VMM),是一种特殊的软件,它允许在同一台物理计算机上运行多个操作系统 (OS)。对于这些操作系统中的每一个,虚拟机监控程序都会创建一个它们运行在自己的物理计算机上的假象,这被称为虚拟机 (VM)。

虚拟机监控程序运行所在的操作系统称为主机 OS,而在虚拟机上运行的操作系统称为客户 OS。虚拟机监控程序会监视客户 OS 的工作,并(在这些 OS 不知情的情况下)影响它们的启动过程,从而消除它们与主机 OS 之间的冲突。

虚拟机监控程序有两种类型:

  1. 原生或裸机虚拟机监控程序,它们直接与计算机硬件交互,不需要传统的操作系统(此类虚拟机监控程序本身就是一种操作系统,或者可以基于某种精简的通用操作系统来实现)。
  2. 托管虚拟机监控程序,它是运行在传统操作系统(如 Windows 或 Linux)内的软件。

第一类虚拟机监控程序的例子包括 Microsoft Hyper-V(作为 Windows Server 2008 (R2) 的一部分,以及独立的版本 – Microsoft Hyper-V Server 2008 (R2))、VMware ESX(i)、Citrix XenServer 等。第二类虚拟机监控程序包括 VMware Workstation、Oracle(原 Sun)VirtualBox、Microsoft Virtual PC 等。

1.2. Microsoft Hyper-V 虚拟机监控程序

Microsoft Hyper-V 虚拟机监控程序于 2008 年作为 Windows Server 2008 的一部分推出,并且还发布了一个免费的独立版本 – Microsoft Hyper-V Server 2008。它也包含在这些产品的 R2 (Release 2) 版本中。Hyper-V 仅在兼容 x86-64 的处理器上运行(不支持 IA-64 架构),并使用支持虚拟化技术(AMD-V 或 Intel VT)的 AMD64/EM64T 技术。

对于 Windows Server 2008 (R2),虚拟机监控程序被安装为一个单独的角色。它通过标准管理控制台进行管理。此外,还可以通过 Microsoft WMI 技术对虚拟机监控程序进行程序化控制。

2. Hyper-V 虚拟机监控程序的程序化控制

2.1. WMI 技术

WMI 技术(Windows Management Instrumentation)是 Microsoft 对 Web-Based Enterprise Management (WBEM) 和 Common Information Model (CIM) 标准的实现,这些标准由 Distributed Management Task Force (DMTF) 制定。WMI 是一个用于管理各种 Windows OS 组件以及第三方产品的程序化基础设施。WMI 技术的设计考虑了其与不同编程语言(C/C++、Visual Basic、脚本语言(如 VBScript 或 JScript)以及 .NET 系列语言(例如 C#))结合使用的可能性。因此,开发人员可以在其程序中使用 WMI 来执行特定任务,而管理员则可以在其脚本中使用 WMI 来优化他们的日常工作。WMI 支持管理本地和远程计算机。

COM/DCOM 技术是 WMI 技术的基础,因此在学习 WMI 之前了解它们很有用。WMI 使用面向对象的模型,其中被管理的组件表示为一组相互连接的对象,这些对象拥有自己的属性和方法。所有对象构成一个对象数据库,称为CIM 存储库。一种特殊的类 SQL 查询语言 – WQL (WMI Query Language) – 用于管理该存储库。

如果某个 Windows 组件或应用程序需要支持 WMI 管理接口,就需要一个特殊的程序组件(称为 WMI 提供程序)。Microsoft 在 Windows OS 中提供了大量的标准 WMI 提供程序,例如 Active Directory 提供程序、DNS 提供程序、事件日志提供程序、安全提供程序、系统注册表提供程序、虚拟化提供程序、Windows Installer 提供程序等。此外,SQL Server、Exchange Server 等产品也会安装自己的 WMI 提供程序。

虚拟化提供程序旨在管理 Hyper-V 虚拟机监控程序,将在本文中进行详细介绍。

我建议您在使用此技术与虚拟化提供程序或任何其他提供程序进行交互之前,先熟悉 WMI 的基本知识。

2.2. 用于 Hyper-V 管理的主要 WMI 对象描述

让我们简要介绍一下虚拟化提供程序提供的一些 WMI 类。

Msvm_ComputerSystem WMI 类是 Hyper-V 对象模型的基础。此类表示物理计算机或虚拟机。该类有很多属性,让我们来看其中一些:

  • Caption 属性包含“Virtual Machine”字符串(如果类表示虚拟机)或“Hosting Computer System”字符串(如果表示物理机)。在编写 WQL 查询到提供程序时,我们可以通过此属性值来区分虚拟机和物理机。
  • ElementName 属性包含 VM 的显示名称(在管理控制台的 VM 列表中显示)或物理机的 NETBIOS 名称。
  • Name 属性包含 VM 的唯一标识符 (GUID)。
  • EnabledState 属性定义了 VM 的状态(停止、运行、暂停等)。

Msvm_ComputerSystem 类的 RequestStateChange 方法允许修改 VM 的状态(例如,启动或停止它)。

Msvm_VirtualSystemManagementService 类代表虚拟化服务,用于创建、删除和修改 VM,以及创建 VM 快照、导出和导入 VM。让我们来看一下该类的一些方法:

Msvm_VirtualSystemSettingData 类提供了一组属性,这些属性表示 VM 的低级设置,例如:

  • AutoActivate – 定义 VM 在虚拟机监控程序启动时是否自动启动。
  • BIOSNumLock – 定义 VM 启动时 Num Lock 键的状态。
  • BootOrder – 定义启动设备(硬盘、CD-ROM 等)的顺序。

Msvm_VirtualSystemSettingData 类的每个实例都对应 VM 本身或其某个快照。这由 SettingType 属性的值决定(值为 3 表示 VM,值为 5 表示快照)。SystemName 属性包含与 Msvm_VirtualSystemSettingData 类给定实例对应的 VM 的 GUID。因此,我们可以通过过滤 Msvm_VirtualSystemSettingData 类的所有实例,并按 SystemNameSettingType 属性的值进行筛选,来查找给定 VM 的所有快照。

Msvm_ResourceAllocationSettingData 类表示与某个 VM 相关联的虚拟资源,例如虚拟硬盘、IDE 控制器、网卡、COM 端口等。ResourceType 属性定义了此资源的类型,而 Connection 属性定义了与此资源对应的“物理实体”。因此,对于虚拟驱动器,此属性包含指向相应 VHD 文件(磁盘映像)的路径。

有关这些类和其他类的更多信息,请参阅 MSDN。

2.3. 连接到虚拟机监控程序

让我们看看如何使用 C++ 实现基本的 Hyper-V 管理操作。由于 WMI 技术基于 COM,我们将使用 ATL 库(ATL – Active Template Library – 是 Microsoft Visual Studio 的一部分,包含大量工具集,可用于解决 COM 开发中的典型开发任务)。实际上,我们只使用一些简单的 COM 类型包装类,例如 CComPtr(COM 接口的“智能”指针,会自动调用接口的 AddRefRelease 方法)和 CComBSTR(BSTR 字符串类型的包装器)。

我们还将定义一个辅助宏 CHK_HRES(来自“check HRESULT”),用于检查 COM 错误。每个 COM 方法都必须返回一个特殊的 HRESULT 类型状态。我们可以在每次调用 COM 方法后分析此状态并执行相应的处理。但为了简化代码,最好使用一个宏,它可以自动分析状态并在出错时抛出异常。CHK_HRES 宏定义如下:

#define CHK_HRES(op) \
    { \
        HRESULT tmp_hresult___ = op; \
        if (FAILED(tmp_hresult___)) \
            throw CAtlException(tmp_hresult___); \
    }

CAtlException 异常类,它只存储相应的 HRESULT 状态。我们可以在任何合适的地方处理抛出的异常。我们将按照以下方式使用 CHK_HRES 宏:

CHK_HRES(pObject->Method());

那么,让我们继续进行第一个任务,即连接到虚拟机监控程序。

CComBSTR namespacePath = L"\\\\";
namespacePath += pServer;
namespacePath += L"\\root\\virtualization";

CComPtr<IWbemLocator> pWbemLocator;

CHK_HRES(pWbemLocator.CoCreateInstance(CLSID_WbemLocator, 
    NULL, 
    CLSCTX_INPROC_SERVER));

CComPtr<IWbemServices> pWbemServices;

CHK_HRES(pWbemLocator->ConnectServer(
    namespacePath, 
    CComBSTR(pUserName),
    CComBSTR(pPassword), 
    NULL, 
    0, 
    NULL, 
    NULL, 
    &pWbemServices
    ));

在这个具体的示例中,pServer 是一个包含 Hyper-V 服务器网络地址的字符串。它可以是 IP 地址或网络中的计算机名称。pUserName 是用户名,pPassword 是密码,用于连接到服务器。用户名可以是“DOMAIN\USER”格式。如果我们向 ConnectServer 方法传递 NULL 作为用户名和密码,则将使用当前用户进行连接。

ConnectServer 方法返回一个指向 IWbemServices WMI 接口的指针(pWbemServices),该接口表示给定 Hyper-V 服务器提供的 WMI 服务。

要断开与服务器的连接,应通过调用 Release 方法来释放 IWbemServices 接口的指针(CComPtr 类在其析构函数中会自动执行此操作)。

2.4. 获取虚拟机列表

要获取服务器所有虚拟机列表,需要构造相应的 WQL 查询。如 2.2 节所述,Msvm_ComputerSystem WMI 类对应虚拟机。但由于此类不仅可以表示虚拟机,还可以表示 Hyper-V 服务器本身,因此我们应该按 Caption 属性的值进行过滤。

CComPtr<IEnumWbemClassObject> pEnumWbemClassObject;

CHK_HRES(m_pWbemServices->ExecQuery(
    CComBSTR(L"WQL"),
    CComBSTR(L"select * from Msvm_ComputerSystem where Caption=\"Virtual Machine\""),
    WBEM_FLAG_FORWARD_ONLY,
    NULL,
    &pEnumWbemClassObject
    ));

while (true)
{
    CComPtr<IWbemClassObject> pCurVM;
    ULONG returnCount = 0;

    CHK_HRES(pEnumWbemClassObject->Next(WBEM_INFINITE, 
     1, 
     &pCurVM, 
     &returnCount));
    if (returnCount == 0)
        break;

    // Do something with pCurVM
    // ...
}

在此示例中,我们使用 IWbemServices 接口的 ExecQuery 方法,该方法允许执行 WQL 查询并以枚举器对象的形式返回结果。后者实现了 IEnumWbemClassObject WMI 接口。我们可以通过使用此枚举器来接收实现 IWbemClassObject WMI 接口的对象列表。在我们的例子中,这些对象将是代表虚拟机的对象。

2.5. 获取虚拟机属性

IWbemClassObject 接口有一个 Get 方法,它允许获取相应对象属性的值。属性可以是不同的类型(例如,字符串或数字),但只有一个方法适用于所有属性。因此,该方法以 VARIANT COM 类型形式返回属性值,这允许表示不同类型的值。我们将使用 CComVariant 类 – VARIANT 类型的 ATL 包装器。

CComPtr<IWbemClassObject> pVmObject;

...

CComVariant name;

CHK_HRES(pVmObject->Get(L"Name", 0, &name, NULL, NULL));
if (name.vt == VT_BSTR)
{
    // Use virtual machine’s UUID
    // ...
}
else
{
    // Invalid property type
    // ...
}

在上面的示例中,我们获取 pVmObject 虚拟机 Name 属性(唯一标识符)的值。我们期望此属性的值将以字符串形式返回。这就是为什么最好在 Name 变量中检查我们获得的值是否是所需类型,以确保代码的可靠性。

此代码可用于获取 IWbemClassObject 接口表示的其他对象(不仅是虚拟机)的属性。

2.6. 启动、停止和检查虚拟机状态

现在我们可以读取虚拟机的属性并了解其状态。Msvm_ComputerSystem 类有一个 EnabledState 属性。它的值是一个数字,描述了虚拟机的当前状态。有一组保留常量描述了 VM 的不同状态。例如,2 表示 VM 已启动,3 表示已关闭(有关更多信息,请参阅 MSDN)。

因此,只需读取 VM 的 EnabledState 属性(Get 方法必须返回 VT_14 VARIANT 类型值)并将其与 2 进行比较,即可检查虚拟机当前是否已启动。

让我们处理更复杂的事情。您无法更改 EnabledState 属性的值来启动或停止 VM,因为此属性是只读的。Msvm_ComputerSystem 类有一个 RequestStateChange 方法用于更改 VM 状态。

调用 WMI 方法比获取属性更复杂。要调用方法,我们需要执行以下操作:

  1. 获取代表方法输入参数的对象。
  2. 通过设置此对象的相应属性值来设置输入参数的值。
  3. 调用方法并(如有必要)接收代表方法输出参数的对象。
  4. 通过读取此对象的相应属性值来获取输出参数的值。

那么,让我们获取代表 RequestStateChange 方法输入参数的对象。

CComPtr<IWbemClassObject> pClass;
CComPtr<IWbemClassObject> pInParamsDefinition;
CComPtr<IWbemClassObject> pInParams;

CHK_HRES(pWbemServices->GetObject(CComBSTR(L"Msvm_ComputerSystem"), 
     0, NULL, &pClass, NULL));
CHK_HRES(pClass->GetMethod(pMethodName, 0, &pInParamsDefinition, NULL));
CHK_HRES(pInParamsDefinition->SpawnInstance(0, &pInParams));

首先,我们使用 GetObject 方法获取代表 Msvm_ComputerSystem 类的对象的指针。此对象不代表任何特定的虚拟机或服务器,而是代表类本身。

然后,使用 GetMethod,我们获取有关所需方法输入参数的信息。这些信息包含在 pInParamsDefinition 指针指向的对象中(在此示例中,我们不获取有关输出参数的信息,因为我们不关心它们)。

最后,使用 SpawnInstance 方法,我们创建一个代表输入参数值的对象。

现在我们可以设置 RequestStateChange 方法的输入参数值。我们只有一个这样的参数。它是 RequestState 参数。它可以具有与 Msvm_ComputerSystemEnabledState 属性相同的值。

CHK_HRES(pInParams->Put(L"RequestedState",  0, &CComVariant(state), 0)); 

这里 state 是我们即将设置的 VM 的状态代码。现在几乎准备好调用方法了。

CComVariant path;
CHK_HRES(pVmObject->Get(L"__PATH&, 0, &path, NULL, NULL));
    
CComPtr<IWbemClassObject> pOutParams;

CHK_HRES(m_pWbemServices->ExecMethod(path.bstrVal, 
                                     CComBSTR(L"RequestStateChange"), 
                                     0, 
                                     NULL, 
                                     pInParams, 
                                     &pOutParams, 
                                     NULL));

我们使用 IWbemServices 接口的 ExecMethod 方法来调用 Msvm_ComputerSystem 类的。ExecMethod 接收以下参数:要调用其方法的对象的存储库路径(我们通过读取 VM 的 __PATH 属性来获取 VM 的对象路径);方法名称;以及存储输入参数值对象的指针。执行后,ExecMethod 返回一个指向存储输出参数值对象的指针。我们可以通过读取此对象的相应属性来获取输出参数的值。

RequestStateChange 有一个 Job 输出参数。如果方法是异步执行的,它包含一个指向对象的引用,通过该对象可以跟踪任务执行。但我们不会详细介绍它。使用此参数的示例在随文章一起提供的文件中。

值得一提的是,通过将 VM 状态更改为 Disabled (3),我们将关闭 VM 而不保存任何数据(就像我们拔掉了物理计算机的电源线一样)。在大多数情况下,我们希望以更温和的方式关闭 VM,例如通过客户 OS(就像我们在客户 OS 中单击“开始”–“关机”)。幸运的是,Hyper-V 提供了这种可能性,并为此提供了特殊的 Msvm_ShutdownComponent 类。

Msvm_ShutdownComponent 类有一个 InitiateShutdown 方法,它会启动 VM 的关机(顾名思义)。但是,为了使用它,我们必须找到与我们的虚拟机对应的 Msvm_ShutdownComponent 类的实例。Msvm_ComputerSystemMsvm_ShutdownComponent 类的实例之间没有直接引用,但它们通过特殊的 Msvm_SystemDevice 类连接。在 WMI 术语中,Msvm_ShutdownComponent 类的实例与 Msvm_ComputerSystem 类的实例相关联。WQL 语言中有一个特殊的 ASSOCIATORS OF 操作符用于搜索关联对象(有关更多信息,请参阅 MSDN)。让我们看看如何找到 Msvm_ShutdownComponent 类的所需实例:

CComVariant path;
CHK_HRES(pVmObject-"Get(L"__RELPATH", 0, &path, NULL, NULL));

CStringW query;
query.Format(L"associators of {%s} where AssocClass=Msvm_SystemDevice"
                                      L" ResultClass=Msvm_ShutdownComponent", 
             path.bstrVal);
    
CComPtr<IEnumWbemClassObject> pEnum;

CHK_HRES(m_pWbemServices->ExecQuery(
    CComBSTR(L"WQL"),
    CComBSTR(query),
    WBEM_FLAG_FORWARD_ONLY,
    NULL,
    &pEnum
    ));

CComPtr<IWbemClassObject> pShutdownComponent;
ULONG returnCount = 0;

CHK_HRES(pEnumWbemClassObject->Next(WBEM_INFINITE, 
                                    1, 
                                    &pShutdownComponent, 
                                    &returnCount));
if (returnCount == 0)
{
    // Msvm_ShutdownComponent not found
    // ...
}

首先,我们获取 VM 在存储库中的路径。然后,我们构造 WQL 查询以获取 Msvm_ShutdownComponent 类的关联实例,并调用 ExecQuery 方法来执行查询。最后,我们从枚举器对象中获取 Msvm_ShutdownComponent 的指针。

现在我们可以关闭 VM 了。

CComVariant shutdownComponentPath;
CHK_HRES(pShutdownComponent->Get(L"__RELPATH", 0, &shutdownComponentPath,
    NULL, NULL));

CComPtr<IWbemClassObject> pShutdownComponentClass;
CComPtr<IWbemClassObject> pInParamsDefinition;
CComPtr<IWbemClassObject> pInParams;

CHK_HRES(m_pWbemServices->GetObject(
    CComBSTR(L"Msvm_ShutdownComponent"), 
    0, 
    NULL, 
    &pShutdownComponentClass, 
    NULL
    ));
        
CHK_HRES(pShutdownComponentClass->GetMethod(L"InitiateShutdown", 0,
   &pInParamsDefinition, NULL));
CHK_HRES(pInParamsDefinition->SpawnInstance(0, &pInParams));

CHK_HRES(pInParams->Put(L"Force", 0, &CComVariant(true), 0));
CHK_HRES(pInParams->Put(L"Reason", 0, &CComVariant(L"Shutdown"), 0));

CHK_HRES(m_pWbemServices->ExecMethod(
    shutdownComponentPath.bstrVal,
    CComBSTR(L"InitiateShutdown"), 
    0, 
    NULL, 
    pInParams, 
    NULL,
    NULL
    ));

3. 结论

在本文中,我们使用 C++ 语言和 WMI 技术探讨了 Microsoft Hyper-V 虚拟机监控程序的程序化控制问题。文章中给出的示例将帮助您获得使用 C++ 语言编写的程序对 Hyper-V 进行典型管理的通用思路。当然,在入门文章的篇幅内,我们无法探讨许多更复杂的任务(例如管理 VM 的设备)。但 Hyper-V 的 WMI 接口在 MSDN 上有相当清晰的文档,您可以在其帮助下自行解决任务。此外,您可以搜索到许多使用不同编程语言(包括脚本语言)解决 Hyper-V 管理任务的示例。通常,如果需要,这些示例可以轻松地转换为 C++ 语言。

4. 有用链接

5. 附件 - 示例源代码

附件中的示例包含一个用于管理 Hyper-V 虚拟机监控程序的类库,以及一个使用部分库功能的测试应用程序。

该库包含两个类:CHyperV 类和 CHyperVVirtualMachine 类。CHyperV 类允许连接到 Hyper-V 服务器并获取所有虚拟机或单个虚拟机的列表。CHyperVVirtualMachine 类代表单个虚拟机,允许获取其某些属性并控制其状态。

测试应用程序允许连接到指定的服务器(参数在命令行中设置),并显示所有虚拟机的列表。对于正在运行的虚拟机,还会显示其 DNS 名称和 IPv4 地址。

该示例是为 Microsoft Visual Studio 2008 设计的。

6. 历史记录

© . All rights reserved.