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

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2010 年 2 月 16 日

CPOL

6分钟阅读

viewsIcon

24727

在本文中,我们将继续探讨导出和导入虚拟机等主题。

引言

在本文中,我将继续我在此系列第二部分中留下的内容。本文的主要目标是继续构建 Hyper-V 管理 WMI 类和方法。在这一部分,我主要关注如何执行一些高级操作,例如导出和导入操作。这些操作本质上可以帮助您在应用程序中构建隐式的“克隆”功能。

背景

在上篇文章中,我的重点主要是展示 Hyper-V 的基本操作是如何工作的。我通过了有助于我们更改 VM 状态的步骤,从而为更多操作奠定了基础。在本文中,我们将继续探讨 VM 管理方面的一些更高级的主题,例如导入和导出操作。希望这些概念将帮助您在应用程序中构建更多功能。我并不是说导入和导出操作完成了 VM 支持的所有操作,但它们为您编写其他例程提供了一个良好的开端。这只是冰山一角,一旦您开始探索 root\virtualization 命名空间,您将发现许多新的例程可以集成到您自己的自定义应用程序中。

VM 管理例程和支持例程

使用虚拟机最棒的地方在于能够像操作系统中运行的应用程序或进程一样操作它们。在第二部分的解释基础上,我将继续讲解如何导入和导出 VM,从而为克隆虚拟机提供功能。

导出虚拟机

导出虚拟机允许用户选择特定的 VM,然后导出其状态、虚拟硬盘 (VHD) 文件以及 VM 的配置信息。这使得可以将导出的 VM 传输到另一台系统上进行导入,从而有效地创建父虚拟机的一个克隆。

在下面的代码片段中,我提供了一个通用的函数,可用于导出 VM。大多数 WMI 使用语义与我们在第一部分和第二部分讨论的相同。唯一的区别是此方法使用 VirtualSystemManagementService 类,该类包含大量用于有效管理 VM 的方法。

static void ExportVM(string vmName, string exportDirectory, string Node)
{
     String ConnectionString = 
       @"\\" + Node + @"\" + 
       @"root\virtualization";

     ManagementScope scope = 
       new ManagementScope(ConnectionString, null);
     ManagementObject virtualSystemService = 
       Utility.GetServiceObject(scope, 
       "Msvm_VirtualSystemManagementService");

     ManagementObject vm = Utility.GetTargetComputer(vmName, scope);

     ManagementBaseObject inParams = 
       virtualSystemService.GetMethodParameters(
       "ExportVirtualSystem");
     inParams["ComputerSystem"] = vm.Path.Path;

    //(below)Setting CopyVmState = true 
    //will export the entire VM along with 
    //its state files and Virtual Harddisk files (VHD). 
    //Setting it false will export the VM without 
    //the state files and VHD files

    inParams["CopyVmState"] = true; 
    if (!Directory.Exists(exportDirectory))
     {
        Directory.CreateDirectory(exportDirectory);
     }
    inParams["ExportDirectory"] = exportDirectory;

    ManagementBaseObject outParams = 
      virtualSystemService.InvokeMethod(
      "ExportVirtualSystem", inParams, null);

     if ((UInt32)outParams["ReturnValue"] == ReturnCode.Started)
      {
          if (Utility.JobCompleted(outParams, scope))
           {
             Console.WriteLine("VM '{0}' were " + 
                     "exported successfully.", 
                     vm["ElementName"]);
            }
          else
           {
             Console.WriteLine("Failed to export VM");
           }
      }

     else if ((UInt32)outParams["ReturnValue"] == ReturnCode.Completed)
     {
       Console.WriteLine("VM '{0}' were exported successfully.", 
                         vm["ElementName"]);
     }
     else
     {
       Console.WriteLine("Export virtual system failed " + 
         "with error {0}", outParams["ReturnValue"]);
     }

     //cleanup
     inParams.Dispose();
     outParams.Dispose();
     vm.Dispose();
     virtualSystemService.Dispose();
}

在上面的代码片段中,我已将 ExportVM 函数定义为 static。当您在特定类中使用它时,您可以根据类的设计进行更改。如果您注意到,该方法的前几部分与其他任何 WMI 例程都完全相同。像往常一样,我们使用连接对象连接到范围。我们连接到 root\virtualization 命名空间,这是所有 Hyper-V 虚拟机管理例程的标准命名空间。我们需要初始化和使用的类是 Msvm_VirtualSystemManagementService 类。它为我们提供了多个有用的方法,允许我们对 VM 执行多个操作,包括导出和导入(这也是我们将重点关注的)。

获得代表该类的对象后,我们需要获取 ExportVirtualSystem 方法所需的参数。请记住,此时我们已经初始化了所有管理基础设施,并连接到了特定的 VM 实例(基于提供的 VM 名称)。需要向 VM 传递三个参数——第一个是代表此虚拟机对象的 CIM 引用,我们可以从之前创建的管理对象中获取;第二个参数是是要进行完全导出还是部分导出(我将在之后讨论);最后,最后一个输入参数是要放置导出文件的导出目录的路径。现在,回到参数 CopyVmState - 它基本上告诉系统需要进行完全导出还是部分导出。完全导出意味着将导出与 VM 相关的所有文件——状态文件、虚拟硬盘文件和快照。部分导出只会创建一个包含导出配置信息的容器文件,而不会导出状态和 VHD 文件。由于我们提供了导出目录,该函数首先验证目录路径是否有效,然后如果不存在则创建它。然后将其传递给 ExportDirectory 参数。

一旦我们 InvokeMethod 并执行此方法,我们将在 outParam 中获得返回值。与我们在第二部分所做的相同,我们将使用我们的实用函数来跟踪 ExportVirtualSystem 方法的完成情况。您可以在第二部分中找到此实用函数的代码,或者我在本文末尾提供了它。

导入虚拟机

下一部分是导入已导出的 VM。这与导出过程正好相反。代码流程几乎相同,只是传递给 ExportVirtualSystem 方法的参数有少量更改。ImportVirtualSystem 仅接受两个输入参数——导入目录(与我们导出的目录相同),以及一个名为 GenerateNewID 的布尔值。所有虚拟机都关联有一个虚拟机标识符,该标识符在物理系统上必须是唯一的。同一物理系统上的两个 VM 可以具有相同的名称,但必须具有不同的 ID。但是,当您从一台机器导入到另一台机器时,您仍然可以保留相同的虚拟机 ID。代码片段本身很容易理解,并且与导出方法属于同一类别。

导出函数和导入函数之间的一个小区别是,在导出函数中,我假设它在本地执行,因此不接受用户名和密码参数。但在导入函数中,我也传递了用户名和密码参数。您可以将此部分添加到上面的函数中,使其更完整。

static void ImportVM(string importDirectory, String Node, 
                     string UserName, string Password)
{
    String ConnectionString = @"\\" + Node + 
      @"\" + @"root\virtualization";
    ConnectionOptions co = new ConnectionOptions();
    co.Username = UserName;
    co.Password = Password;
    ManagementScope scope = new ManagementScope(ConnectionString, co);
    scope.Connect();
    ManagementObject virtualSystemService = 
       Utility.GetServiceObject(scope, 
       "Msvm_VirtualSystemManagementService");

    ManagementBaseObject inParams = 
      virtualSystemService.GetMethodParameters(
      "ImportVirtualSystem");
    //I am overriding any passed params to this call. We need same ID
    inParams["GenerateNewID"] = "false";
    inParams["ImportDirectory"] = importDirectory;

    ManagementBaseObject outParams = 
      virtualSystemService.InvokeMethod(
      "ImportVirtualSystem", inParams, null);

    if ((UInt32)outParams["ReturnValue"] == ReturnCode.Started)
    {
        if (Utility.JobCompleted(outParams, scope))
        {
            Console.WriteLine("VM were imported successfully.");
        }
        else
        {
            Console.WriteLine("Failed to import VM");
        }
    }
    else if ((UInt32)outParams["ReturnValue"] == ReturnCode.Completed)
    {
        Console.WriteLine("VM were imported successfully.");
    }
    else
    {
        Console.WriteLine("Import virtual system failed " + 
          "with error {0}", outParams["ReturnValue"]);
    }

    //Cleanup

    inParams.Dispose();
    outParams.Dispose();
    virtualSystemService.Dispose();
}

在很大程度上,上面的代码片段与导出函数类似。我将 GenerateNewID 输入参数设置为 false - 这意味着我们不希望生成新的 VM ID。将其设置为 true 将允许您创建新的机器 ID,在这种情况下,导出的 VM 可以与导出它的 VM 在同一系统上进行导入。

本部分到此结束,我们主要关注了虚拟机(无论是部分还是全部)的导出和导入。通过这些,您可以在自定义的 VM 管理应用程序中实现克隆功能。需要注意的是,某些方法(如 ImportVirtualSystemExportVirtualSystem)在 Windows 2008 R2 及更高版本中已被弃用。它们已被 ImportVirtualSystemExExportVirtualSystemEx 方法取代。我鼓励读者查阅 MSDN 中的 API 文档,了解已发生的变化。

为了使这里的代码完整,我重复了我们在所有函数中使用的 JobCcmplete 函数,以跟踪任务的完成情况。如您所知,导入、导出、停止、启动等任务不是确定性的,也就是说,它们不会在您执行后立即完成。为了跟踪任务的完成,我们有可用的机制,可以在其中等待作业完成。此函数只是通用地使您能够跟踪从执行 WMI 方法返回的任何此类作业对象。

//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;
}

参考文献

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

© . All rights reserved.