Thursday, November 17, 2011

Copying a MasterVM into many with Hyper-V WMI

Now, I am not going to take any credit for this little and brilliant piece of PowerShell.  All the credit goes to Taylor Brown.

Back many moon ago Taylor was publishing a bunch of PowerShell scripts that use the Hyper-V WMI interface to do things.  (In more recent times Ben Armstrong has taken up this task)

Taylor’s original post is here: http://blogs.msdn.com/b/taylorb/archive/2008/06/07/hyper-v-wmi-cloning-virtual-machines-using-import-export.aspx

Did you notice that date?  The year was 2008 on that original post.

This is actually a highly useful bit of code if you want to make bunches of VMs that are identical in settings.  You create the first “MasterVM” – you could also call a template.  Then copy it.

I did tweak Taylor’s PowerShell a bit, but only to remove an error that annoyed me with Write-Progress and to pre-pend and post-pend the VM names, the count to start at and the number to create.

I will add more to this later as this is just the beginning.  After all of this creation of VMs there are other settings that can be applied and considerations that you must have in regards to the system as a whole.

What this script does is use the Hyper-V Export and Import feature.  The Export creates a copy of the VM (and all of its associated physical objects) in a nice tidy folder in the path that is specified.

This target can be local storage or if your target Hyper-V server is a node of a cluster it can be a CSV.  And the plus of using Export to copy is that the VM is all set to be Highly Available.

The Import takes advantage of Hyper-V doing all the work to generate the new virtual objects, apply security settings, modify any snapshots files, handle differencing disks, etc.  Let the system do all that work so I don’t have to.  After all, someone already went to all the trouble to build the method, and it gets tested.

Without further delay, here is my version of the script.

param
(
[string]$masterVm = $(Throw "MasterVM required"),
[string]$exportPath = $(Throw "Path required to the Hyper-V local storage where VMs will be created"),
[string]$namePrePend = $(Throw "A string to add to the front of the new vm name"),
[string]$namePostPend = $(Throw "A string to add to the end of the new vm name"),
[string]$hypervHost = $(Throw "The Hyper-V host where the template VM is registered and where VMs will be created"),
[int] $startAt = $(Throw "The number to begin the VM creation with (ie. 2 = start with VM 2)"),
[int]$numCopies = $(Throw "The number of VMs to create")

)

function ProcessWMIJob
{
    # Is there a way to loop through this and have these process and manage multiple jobs asynchronously?  The problem might be in overloading the storage layer.
    param (
    [System.Management.ManagementBaseObject]$Result
    )

    if ($Result.ReturnValue -eq 4096) {
        $Job = [WMI]$Result.Job

        while ($Job.JobState -eq 4) {
            Write-Progress -Id 2 -ParentId 1 $Job.Caption -Status "Executing" -PercentComplete $Job.PercentComplete
            Start-Sleep 1
            $Job.PSBase.Get()
        }
        if ($Job.JobState -ne 7) {
            Write-Error $Job.ErrorDescription
            Throw $Job.ErrorDescription
        }
    }
    elseif ($Result.ReturnValue -ne 0) {
        Throw $Result.ReturnValue
    }
    Write-Progress -Id 2 -Status "Complete" -Completed $TRUE
}

#Main Script Body
"Creation script is starting at: "+(Get-Date -uFormat "%j, %T")

$VMManagementService = Get-WmiObject -Namespace root\virtualization -Class Msvm_VirtualSystemManagementService -ComputerName $hypervHost
$SourceVm = Get-WmiObject -Namespace root\virtualization -Query "Select * From Msvm_ComputerSystem Where ElementName='$masterVm'" -ComputerName $hypervHost
$a = $startAt

$arrNewVms = @()

write-progress -Id 1 "Cloning Vm's" -Status "Executing"

while ($a -lt ($startAt + $numCopies)) {
    $tempVMName = ($namePrePend + $a.ToString("00000") + $namePostPend)
    $tempVMName
    # a simple test to see if the VM already exists and try the next
    if ((Get-WmiObject -Namespace root\virtualization -Query "Select * From Msvm_ComputerSystem Where ElementName='$tempVMName'" -ComputerName $hypervHost) -eq $NULL) {

        $VMSettingData = Get-WmiObject -Namespace root\virtualization -Query "Associators of {$SourceVm} Where ResultClass=Msvm_VirtualSystemSettingData AssocClass=Msvm_SettingsDefineState" -ComputerName $hypervHost
        $VMSettingData.ElementName = $tempVMName

        $Result = $VMManagementService.ModifyVirtualSystem($SourceVm, $VMSettingData.PSBase.GetText(1))
        ProcessWMIJob $Result

        $Result = $VMManagementService.ExportVirtualSystem($SourceVm, $TRUE, "$exportPath")
        ProcessWMIJob $Result

        $Result = $VMManagementService.ImportVirtualSystem("$exportPath\$tempVMName", $TRUE)
        ProcessWMIJob $Result

        $arrNewVms += $tempVMName
    }
    $a ++

}
write-progress -Id 1 "Cloning Vm's" -Completed $TRUE

$VMSettingData = Get-WmiObject -Namespace root\virtualization -Query "Associators of {$SourceVm} Where ResultClass=Msvm_VirtualSystemSettingData AssocClass=Msvm_SettingsDefineState" -ComputerName $hypervHost
$VMSettingData.ElementName = $masterVm

$Result = $VMManagementService.ModifyVirtualSystem($SourceVm, $VMSettingData.PSBase.GetText(1))
ProcessWMIJob $Result

"The following were created:"
$arrNewVms

7 comments:

BrianEh said...

By the way - the WMI in this script does work with the Windows Server 8 Developer Preview.

(I just happened to try that today).

joice said...

Hi Brian,
I am attempting to clone a bunch of VM's by exporting and then importing a Golden VM. I am using the Windows Server 8 DataCenter. I used the importVirtualSystemEx(...) to import an already exported VM.
I cannot seem to be able to display the Msvm_VirtualSystemImportSettingData object that I obtained using the GetVirtualSystemImportSettingData(...). I am pasting the code sample below

$ExpPath = "C:\Sample_Export"

$VMMgmtService = Get-WmiObject -Namespace "root\virtualization" -Class MSVM_VirtualSystemManagementService

$ImportSettings = $VMMgmtService.GetVirtualSystemImportSettingData($ExpPath)

$ImportSettings= $ImportSettings.ImportSettingData

$ImportSettings.GenerateNewId = $TRUE

$ImportSettings.CreateCopy = $TRUE

At this point im getting an error which says that

"Property 'GenerateNewId' cannot be found on this object; make sure it exists and is settable."

"Property 'CreateCopy' cannot be found on this object; make sure it exists and is settable."

Can you please shed some light on where I am going wrong?

Thanks in Advance!

-Regards,

JJ

BrianEh said...

The one thing that you have to think about here is that this is not PowerShell nor PowerShell objects, it is WMI / CIM.
It is extra literal.
When you do a Get - you are simply defining the object ot get the settings of - this does not contain the methods you are trying to invoke, which appears to be the problem you are running in to.

joice said...

Thanks for the reply Brian! :)
I see that after invoking the GetVirtualSystemImportSettingData($ExpPath) on the VMMMgtService object, i am able to see the field "ImportSettingData" , but it remains empty. so i guess that should be the reason why I am encountering the error. Any ideas as to why this field is empty.

I checked out a few other sources as well(e.g hyperv library on codeplex link : http://pshyperv.codeplex.com/) and those guys also essentially try to do the same thing.

BrianEh said...

I am trying to read through and interpret your request.

What is it that you are attempting to set with ImportSettingData?

I realize that I am simply taking advantage of the default system behaviors and you are trying to specify certain behavior.

Is that it?

What I am guessing is that the ImportSettings need to be built and added to the import command as a hash or string prior to executing the command (passing it back to the server to execute).

Let me see if I can get one of the Hyper-V folks to chime in.

joice said...

Yes. Using the ImportSettings I am trying to set the flags for GenerateNewId, CreateCopy in Msvm_VirtualSystemImportSettingData class(http://msdn.microsoft.com/en-us/library/dd379577(v=vs.85).aspx) and then later set the TargetVhdDataRoot, TargetSnapshotDataRoot, TargetVmDataRoot to a particular directory. Essentially, the hyper v commandlets let you do the same as well(correct me if i am wrong here) e.g import-vm allows you to specify the directory where your vhds can reside. I am attempting to do the same albeit using the WMI objs. Hope I am clear enough.
Regards,
JJ.

BrianEh said...

Thanks Joice.

Have you tried the new PowerShell cmdlets in Server 2012 for Hyper-V?

Also, if you are using new Server 2012 WMI - you need to be sure you are in the v2 namespace (my script here is not).

I am wondering if this is similar to an issue I encountered in the SQL WMI - where there was a method available but it was not implemented (in 2008 R2).

I don't know if these features are available across both versions of Server (2008 / 2012).