Monday, November 28, 2011

Linking VMs to copies of the PVS Collection Target Device template.

This is all about taking a Target Device Template that exists within a Provisioning Server (PVS) Farm Collection and copying it to create target devices from a bunch of VMs.

If you have been following I took a MasterVM on a Hyper-V server and I copied that into a number of differently named / unique virtual machines (this includes local cache VHD and settings).  And then I cycled through powering them on, getting their assigned MAC address and powering them off.

I now want to add those VMs into PVS as Target Devices.

From the last step in my script I have this array of VM Names and MAC addresses; $arrUpdatedVms

One hairy bit of this is adding the PVS cmdlets into the session.  I do this as multiple steps but it could be a one-liner as well.

$installutil = $env:systemroot + '\Microsoft.NET\Framework64\v2.0.50727\installutil.exe'
& $installutil "$env:ProgramFiles\Citrix\Provisioning Services Console\McliPSSnapIn.dll"
Add-PSSnapin McliPSsnapin

Now, I want to iterate through the array and add each VM to the Collection, and I want to duplicate the settings of the Collection Template to save myself work later on.

(one note – this checks for the variable $copyTemplate to match “yes” – this just gives me a choice)

write-progress -Id 1 "Addding the VMs to the PVS collection" -Status "Executing"
foreach($e in $arrUpdatedVms){
    $vm = $e.Split(",")
    $deviceName = $vm[0]
    $deviceMac = $vm[1]
    "siteName=$siteName, collectionName=$collectionName, deviceName=$deviceName, deviceMac=$deviceMac"
    if($copyTemplate -match "yes"){
        #Optimally a Template was created that defines the vDisk and other settings. 
        & Mcli-Add Device -r siteName=$siteName, collectionName=$collectionName, deviceName=$deviceName, deviceMac=$deviceMac, copyTemplate=1
    }
    else{
        #adding the device
        & Mcli-Add Device -r siteName=$siteName, collectionName=$collectionName, deviceName=$deviceName, deviceMac=$deviceMac
    }
}
write-progress -Id 1 "Addding the VMs to the PVS collection" -Completed $TRUE

Don’t blink, this is a pretty fast loop to go through all the VMs.  Especially compared to the copy operation.

Tuesday, November 22, 2011

Hyper-V WMI powering on VMs and getting MAC addresses

Previously I have written about formatting MAC addresses from Hyper-V WMI into a format to be consumed by another program and about duplicating a Master (or Template) VM into multiple virtual machines.
Today I am going to put that together and produce an output that contains the each VM as a name, mac address pair in an array.
My Master / Template VM has the MAC address set to dynamic, so this property is going to repeat. I am also going to take advantage on the Hyper-V process of booting the VM to assign the MAC address.
From my previous duplication script I have an array of VMs: $arrNewVms
$arrUpdatedVms = @()

write-progress -Id 1 "Discovering MAC addresses" -Status "Executing"
foreach ($vmName in $arrNewVms) {
$vm = Get-WmiObject Msvm_ComputerSystem -Filter "ElementName='$vmName'" -Namespace "root\virtualization" -ComputerName $hypervHost
$Result = $vm.RequestStateChange(2) # start
ProcessWMIJob $Result
$vssd = $vm.getRelated("Msvm_VirtualSystemSettingData") | where {$_.SettingType -eq 3}
$vmLegacyEthernet = $vssd.getRelated("Msvm_EmulatedEthernetPortSettingData")
# $vnic = $vm.GetRelated("Msvm_EmulatedEthernetPort") # this only returns while the VM is running and $mac = $e.PermanentAddress
foreach ($e in $vmLegacyEthernet) {
$mac = $e.Address
$macDash = $mac.Substring(0,2) + "-" + $mac.Substring(2,2) + "-" + $mac.Substring(4,2) + "-" + $mac.Substring(6,2) + "-" + $mac.Substring(8,2) + "-" + $mac.Substring(10,2)
$arrUpdatedVms += ($vmName + "," + $macDash)
}
$vm.RequestStateChange(3) # stop
}
write-progress -Id 1 "Discovering MAC addresses" -Completed $TRUE
$arrUpdatedVms
In this I refer to a helper Function “ProcessWMIJob” this is handy in that it forces me to wait until the Start VM process is completed. If I didn’t use this my VMs would not be started and therefore will not in turn power off – it becomes a bit of a timing issue.
The Function can be found at the beginning of the script here.
Next time, something completely different.

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

Monday, November 14, 2011

Hyper-V WMI MAC address to a different format

Here is a simple one that I don’t want to lose.

In my previous post I had mentioned how to get the MAC address that is assigned to the vNIC of a VM.

Get the VM, then find the associated vNIC (and make sure the VM is running):

$vm = Get-WmiObject Msvm_ComputerSystem -Filter "ElementName='$vmName'" -Namespace "root\virtualization" -ComputerName $HyperVHost

$vnicEmulated = $vm.GetRelated("Msvm_EmulatedEthernetPort")

$vnicSynthetic = $vm.GetRelated("Msvm_SyntheticEthernetPort")

In the comments of my previous post Ben Armstrong made the following suggestion (which works kind of nice):

$vssd = $vm.getRelated("Msvm_VirtualSystemSettingData") | where {$_.SettingType -eq 3}
    $vmLegacyEthernet = $vssd.getRelated("Msvm_EmulatedEthernetPortSettingData")

The MAC address is the “PermanentAddress” property of the $vnic and the “Address” property of the $vmLegacyEthernet – so pay attention to the property you are fetching.

A quick way to pluck it out is just to loop through the collection like this:

foreach ($e in $vnic) {
    $mac = $e.PermanentAddress
}

This gives you a MAC address that looks like this:  00155D680A14

This is all fine and dandy if the reason you need the MAC can deal with that format.  And mine can’t.  I must have it defined with dashes.

I went searching for fancy REGEX ways to handle this and could not come up with anything.  But some nice validators for MAC addresses.  The best in the Splunk forums.

So, here is my less sophisticated but useful part:

$macDash = $mac.Substring(0,2) + "-" + $mac.Substring(2,2) + "-" + $mac.Substring(4,2) + "-" + $mac.Substring(6,2) + "-" + $mac.Substring(8,2) + "-" + $mac.Substring(10,2)

This gives me the following result:  00-15-5D-68-0A-14

Just add that into the foreach loop above and move on.

Friday, November 11, 2011

Hyper-V WMI association NULL returns and stopped VMs

I spent way too much time staring at this today until it dawned on me that my VM might need to be running for certain associations to be activated and working.

All I was trying to do is to get a PowerShell script to return the MAC address of the Legacy Network Adapter of a VM.

Ben Armstrong has been more than generous at giving samples to the community and the updated associations sample (that should have given me exactly what I wanted) is here.

In his example he merrily selects his VM:

$vm = gwmi MSVM_ComputerSystem -filter "ElementName='Home Server'" -namespace "root\virtualization"

Then the very easily returns the vNIC:

$vnic = $vm.GetRelated("MSVM_EmulatedEthernetPort")

That is all fine and dandy for the example.  But I tried it on a VM that is powered off, and you know what?  I got nothing back.  A NULL came back from WMI.

I searched for quite a long time trying to figure out what I was doing wrong.

In the end, I discovered I was doing nothing wrong.  The VM has to be running for this association to return the vNIC. 

I actually find this interesting.  As once the object is created, it exists, and it has properties.  I already powered the VM on and off for the MAC address to be automatically assigned.  And then I can’t query it.  It makes little sense to me.  Maybe I can get Ben to respond and fill in that lack in my understanding.

In the end I simply modified my script.  I have to briefly power on the VM and then off again to automatically assign the MAC, so why not take advantage of that.

Here is what I simply have now:

$vm = Get-WmiObject Msvm_ComputerSystem -Filter "ElementName=$vmName" -Namespace "root\virtualization"
$vm.RequestStateChange(2) # start
$vnic = $vm.GetRelated("Msvm_EmulatedEthernetPort") # this only returns while the VM is running
$vm.RequestStateChange(3) # stop

Wednesday, November 2, 2011

A dollar for your error

Last week I was trying desperately to understand why something was failing in a PowerShell script of mine.

I am still trying to figure a good way of understand what the problem is, but I ran across a nifty PowerShell feature that I had never heard of before.

$error

$error is a nifty thing.  It is an array with methods.  Within your script or session it is always capturing your error messages silently in the background and you move along.

Now, the real nifty part of this was when I was trying to capture errors within someone else's cmdlets as commands just kept failing and I could not understand why.

So, before I invoked the command I would clear $error with $error.Clear()

Then execute the command (which always had a clean exit by the way).

Then I would interrogate $error to see what went wrong.  Wow, it was huge.

Come to find out, the creator of the cmdlet that I was trying to find the problem with had –silentlycontinue all over the place.  So there were all kinds of failures but things just plugged along.

$error let me get all this out.  All kinds of errors that were hidden by the developer.  All it did was require a bunch of time on my part to unwind and find the actual error that I required.