Showing posts with label WMI. Show all posts
Showing posts with label WMI. Show all posts

Monday, August 19, 2013

Hyper-V WMI v2 porting guide

Ben Armstrong of the Hyper-V team just released (on the TechNet Wiki) a v2 namespace porting guide.

To all of my DevOps and developer friends out there, this is a highly useful guide.  Because if you have not noticed, moving from the v1 namespace to the v2 is not a simple change of the namespace.

And if you have not heard yet, the v1 namespace is GONE with the 2012 R2 release.

So, spend some time over at the TechNet Wiki page;

http://social.technet.microsoft.com/wiki/contents/articles/19192.hyper-v-wmi-v2-porting-guide.aspx

and then go over to Taylor Brown’s blog and check his updates of his v1 namespace examples to the v2 namespace:

http://blogs.msdn.com/b/taylorb/

Thursday, December 6, 2012

Setting VM IP with Hyper-V WMI

There is a hidden feature in Server 2012.  Buried deep in the bowels of the WMI classes is this little tidbit that is discoverable, but not talked about.

You can set the IP of a VM through Hyper-V 2012. 

For a long time now, in the forums we have told folks that this cannot be done.  Well, it can be.  And at small scale it works.  I have used it to set up my Network Virtualization demonstration environment.

The thing that you need to be aware of is that this is one of those cases where you send your command to WMI / CIM and you need to double back and check what happened.  Did the IP actually set? 

Be sure to check that before going off and expecting that it did.  If it didn’t, you have event logs in the VM.  Check there.

This took a bit of working through and discovery.  I never did work through IPv6, I stopped when I got IPv4 working.

<speculation>

As you might imagine, there has to be a dependency on the aligning of the version of the Integration Services within the VM and the Hyper-V Server.  I could not imagine this working with Hyper-V 2012 and a Server 2008 VM when the Integration Components in the VM have not been updated to the current Hyper-V level.

</speculation>

One thing that I did not blog about with the Network Virtualization script was how I set up my environment, more on that next, I scripted it, and it is not small.

Back the the subject, setting the IP of a VM through WMI of the Hyper-V Server.  I am going to leave out all the error handling just to make this easier to read through.

Here is a PowerShell function where you can see the WMI in action:

function Set-VMIPAddress ($VMName, $IPSettings)

{

    $Service = Get-WmiObject -Class "Msvm_VirtualSystemManagementService" -Namespace "root\virtualization\v2"

    $Query = "SELECT * FROM Msvm_ComputerSystem WHERE ElementName = '" + $VMName + "'"

    $VM = Get-WmiObject -Query $Query -Namespace "root\virtualization\v2"

    $setIP = $Service.SetGuestNetworkAdapterConfiguration($VM, $IPSettings.GetText(1))

}

Of course, you want to consider $setIP.ReturnValue

Like all WMI / CIM commands if the return value is “0” then you have success, “4096” generally means it is running.  And anything else means something went wrong in most cases.

If you have “4096” then query for the status of the job itself.  $job = [WMI]$setIP.job

By the way, if you want to get the IP of the VM don’t think $Service.GetGuestNetworkAdapterConfiguration.  This is proper thinking for PowerShell but not for WMI / CIM.  CIM could never be that easy.  ;-)

Look for the class Msvm_GuestNetworkAdapterConfiguration to get the IP of a VM.  Find the VM NIC with Msvm_SyntheticEthernetPortSettingData first.

Tuesday, January 17, 2012

psbase.gettext(1) I love you

I have had many cases to do things in PowerShell where I am manipulating CIM or WMI objects.
Getting CIM/WMI objects and querying information from them is never a big deal, however modifying an object and then trying to send it back to a method to cause some setting change has always been a pain.
When you get a CIM/WMI object with PowerShell, PowerShell treats it as an object, where in actuality it is XML.  It is a representation of am object.  It has a path and properties.
Over time I have treated this all kinds of ways to get it to format properly for the receiving CIM/WMI.
You can deal with it as InnerText, you can convert it to XML, you can deal with is as a complex string @” <stuff in there with intact formatting> ”@
When PowerShell sees the WMI as an object you can manipulate it in an easy object type of way.  But you can’t send it back to a WMI method.
When PowerShell sees the CIM as a string, you can manipulate it in a painful way, but you can easily send it back to a CIM method.
As a consumer who uses both Windows CIM (WMI) and Linux CIM (CIM) this is a royal pain.  Especially when PowerShell automatic object handling does not recognize the return properly (which I have had happen with CIM lots of times).  Now, I know that PowerShell 3 (in the Win8 Developer Preview) will make this all much better, but I am not there yet.
Lets just take a quick look at the object and return.
When you perform a query against WMI you get a single object or an array of objects back.  But PowerShell treats them like objects.  You can “.” the properties and methods.  This is powerful.
PS C:\Users\Public\Documents\> $vsmssd

__GENUS                    : 2
__CLASS                    : Msvm_VirtualSystemManagementServiceSettingData
__SUPERCLASS               : CIM_SettingData
__DYNASTY                  : CIM_ManagedElement
__RELPATH                  : Msvm_VirtualSystemManagementServiceSettingData.InstanceID="Microsoft:BJEDAR2"
__PROPERTY_COUNT           : 13
__DERIVATION               : {CIM_SettingData, CIM_ManagedElement}
__SERVER                   : BJEDAR2
__NAMESPACE                : root\virtualization
__PATH                     : \\BJEDAR2\root\virtualization:Msvm_VirtualSystemManagementServiceSettingData.InstanceID="Microsoft:BJEDAR2"
BiosLockString             :
Caption                    : Hyper-V Virtual System Management Service
DefaultExternalDataRoot    : C:\Users\Public\VMs\
DefaultVirtualHardDiskPath : C:\Users\Public\VMs\
Description                : Settings for the Virtual System Management Service
ElementName                : Hyper-V Virtual System Management Service
InstanceID                 : Microsoft:BJEDAR2
MaximumMacAddress          : 00155D748DFF
MinimumMacAddress          : 00155D680A00
NumaSpanningEnabled        : True
PrimaryOwnerContact        :
PrimaryOwnerName           : Administrators
ScopeOfResidence           :
But when I modify that object and send it back to the WMI provider I need to adhere to the CIM standard and I cannot just send back $vsmssd.   That is where this little, wonderful, psbase.gettext method comes into play.  Lets take this object and turn it back into its CIM text so that I can send it back to the WMI provider.
When I type that out to the screen I get something very different, not an object, but useful for sending the message.
PS C:\Users\Public\Documents\> $vsmssd.psbase.gettext(1)
<INSTANCE CLASSNAME="Msvm_VirtualSystemManagementServiceSettingData"><QUALIFIER NAME="dynamic" PROPAGATED="true" TYPE="boolean" TOSUBCLASS="false" TOINSTANCE="true"><VALUE>TRUE</VALU
E></QUALIFIER><PROPERTY NAME="__PATH" CLASSORIGIN="___SYSTEM" TYPE="string"><VALUE>\\BJEDAR2\root\virtualization:Msvm_VirtualSystemManagementServiceSettingData.InstanceID="Microsoft:
BJEDAR2"</VALUE></PROPERTY><PROPERTY NAME="__NAMESPACE" CLASSORIGIN="___SYSTEM" TYPE="string"><VALUE>root\virtualization</VALUE></PROPERTY><PROPERTY NAME="__SERVER" CLASSORIGIN="___S
YSTEM" TYPE="string"><VALUE>BJEDAR2</VALUE></PROPERTY><PROPERTY.ARRAY NAME="__DERIVATION" CLASSORIGIN="___SYSTEM" TYPE="string"><VALUE.ARRAY><VALUE>CIM_SettingData</VALUE><VALUE>CIM_
ManagedElement</VALUE></VALUE.ARRAY></PROPERTY.ARRAY><PROPERTY NAME="__PROPERTY_COUNT" CLASSORIGIN="___SYSTEM" TYPE="sint32"><VALUE>13</VALUE></PROPERTY><PROPERTY NAME="__RELPATH" CL
ASSORIGIN="___SYSTEM" TYPE="string"><VALUE>Msvm_VirtualSystemManagementServiceSettingData.InstanceID="Microsoft:BJEDAR2"</VALUE></PROPERTY><PROPERTY NAME="__DYNASTY" CLASSORIGIN="___
SYSTEM" TYPE="string"><VALUE>CIM_ManagedElement</VALUE></PROPERTY><PROPERTY NAME="__SUPERCLASS" CLASSORIGIN="___SYSTEM" TYPE="string"><VALUE>CIM_SettingData</VALUE></PROPERTY><PROPER
TY NAME="__CLASS" CLASSORIGIN="___SYSTEM" TYPE="string"><VALUE>Msvm_VirtualSystemManagementServiceSettingData</VALUE></PROPERTY><PROPERTY NAME="__GENUS" CLASSORIGIN="___SYSTEM" TYPE=
"sint32"><VALUE>2</VALUE></PROPERTY><PROPERTY NAME="BiosLockString" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="t
rue" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER></PROPERTY><PROPERTY NAME="Caption" CLASSORIGIN="CIM_ManagedElement" TYPE="string"><QUALIFIER NAME="CIMTYPE" PRO
PAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>Hyper-V Virtual System Management Service</VALUE></PROPERTY><PROPERTY NAME="DefaultExternalData
Root" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QU
ALIFIER><VALUE>C:\Users\Public\VMs\</VALUE></PROPERTY><PROPERTY NAME="DefaultVirtualHardDiskPath" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="string"><QUALIFIE
R NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>C:\Users\Public\VMs\</VALUE></PROPERTY><PROPERTY NAME="Description" CLASSOR
IGIN="CIM_ManagedElement" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>Settings for the Virtual S
ystem Management Service</VALUE></PROPERTY><PROPERTY NAME="ElementName" CLASSORIGIN="CIM_ManagedElement" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINS
TANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>Hyper-V Virtual System Management Service</VALUE></PROPERTY><PROPERTY NAME="InstanceID" CLASSORIGIN="CIM_SettingData" TYPE="strin
g"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><QUALIFIER NAME="key" PROPAGATED="true" TYPE="boolean" OVERRIDABLE="fa
lse" TOINSTANCE="true"><VALUE>TRUE</VALUE></QUALIFIER><VALUE>Microsoft:BJEDAR2</VALUE></PROPERTY><PROPERTY NAME="MaximumMacAddress" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSe
ttingData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>00155D748DFF</VALUE></PROPERTY><PROPERTY
NAME="MinimumMacAddress" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE
>string</VALUE></QUALIFIER><VALUE>00155D680A00</VALUE></PROPERTY><PROPERTY NAME="NumaSpanningEnabled" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="boolean"><QUA
LIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>boolean</VALUE></QUALIFIER><VALUE>TRUE</VALUE></PROPERTY><PROPERTY NAME="PrimaryOwnerContact" CLASSORIG
IN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>
</VALUE></PROPERTY><PROPERTY NAME="PrimaryOwnerName" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSettingData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="stri
ng" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER><VALUE>Administrators</VALUE></PROPERTY><PROPERTY NAME="ScopeOfResidence" CLASSORIGIN="Msvm_VirtualSystemManagementServiceSetti
ngData" TYPE="string"><QUALIFIER NAME="CIMTYPE" PROPAGATED="true" TYPE="string" TOINSTANCE="true"><VALUE>string</VALUE></QUALIFIER></PROPERTY></INSTANCE>
The actual object is the same thing, in spite of this last looking like a bunch of XML.
If you try to manipulate CIM from a Linux provider with PowerShell you will see lots of this type of output and dealing with it properly is the pain.
Just thought I would share.  Eventually I will get back to Linux CIM interfaces, but I hope I can wait until PowerShell 3 comes out, with handling for CIM, not just WMI.

Thursday, December 15, 2011

Changing a VM vNIC MAC from dynamic to static

Through my VM creation exercise I am taking advantage of Hyper-V and its practice of assigning MAC address from a pool so that I don’t need to manage all that in my script.

Well, if you have not noticed, I have Provisioning Server in my environment.  And that means that I need to have static MAC addresses.  And I am using Export and Import to copy my MasterVM.

This means that I cannot set the MasterVM with a static MAC.  I would at least have to modify it to have a dynamic and then still set the individual VMs to static.

So, I just set the MasterVM to dynamic and do the rest when I assign the MAC to the VM by powering it on and off (I power the VM on to cause Hyper-V to assign the MAC to the vNIC – again, taking advantage of what the system does for me).

I have this array of new virtual machine names; $arrNewVms that I want to loop through.  I then power on each VM (I have to wait for the power on to complete), then I power off, then I modify the MAC.

Here is where things get tricky.  To discover the MAC of the VM I had previously used an association class this does not return a modifiable object.  So I have to actually get the VM vNIC, modify it, and then send it back to the ModifyVirtualSystemResources method.

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") #This returns information not an actionable object
    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
   
    #Set the MAC to static by first getting the vNIC object
    $vnic = Get-WmiObject Msvm_EmulatedEthernetPortSettingData -Filter "Address='$mac'" -Namespace "root\virtualization" -ComputerName $hypervHost
    $vnic.StaticMacAddress = 1
    $VMManagementService.ModifyVirtualSystemResources($vm.__PATH, $vnic.psbase.gettext(1))
}

Dealing with the Hyper-V default MAC pool

This is my little hack around dealing with the Hyper-V default MAC address pool range.

If you have been following, I have a bunch of virtual machines that I am creating.  And it is important that each has a unique MAC address.

Here is where software behavior gets into my way.

By default a Hyper-V Server MAC address range is only 255 MAC addresses ( 00 – FF ).  But a Hyper-V server can support 385 VMs, or VMs can have multiple virtual NICs.  So my pool can get depleted pretty quickly.

The other issue is that when assigning a MAC address to a VM the MACs are taken from the pool sequentially.  Not a big issue, and it actually adds some predictability.  However, once the end of the list is reached it starts over from the beginning.

The last issue, a Hyper-V Server only reserves the MAC of a VM when that VM is on that host.  Here is where my scenario makes all the problems.  I have a cluster of Hyper-V Servers, I am using one of them to create all my VM copies, then my VMs will migrate away.  Once the VM is no longer on the host where it was created its MAC is marked available in the pool (this is not tracked at the cluster level).

Combine this with the circular assignment action and this MAC can be assigned a second time, to a different VM.

SCVMM can solve this, as it tracks static MACs for all VMs that it manages.  But why use SCVMM just to prevent MAC duplication?

My hack?  Make the pool larger.  Only modify the octet that is necessary to achieve it.  And, randomize it to reduce the possibility of collision from a different host.

In a previous post I modified the MAC pool of a Hyper-V Server.  Here is where I embellish it to help with this duplicate MAC address issue.  Some interesting parts are converting from hexadecimal to decimal and then back again.

Additional background; leave the Hyper-V range "00155D", modify some part of the last 3 octets.  Increasing the next to last octet should give enough MAC addresses to prevent the range from rolling over and cover the number of VMs in a cluster (maximum of 1000).  I just want to make the pool bigger and the last octet of the MaximumMacAddress is always 255 (FF).

$VMManagementService = Get-WmiObject -Namespace root\virtualization -Class Msvm_VirtualSystemManagementService -ComputerName $hypervHost

$vsmssd = Get-WmiObject Msvm_VirtualSystemManagementServiceSettingData -Namespace "root\virtualization" -ComputerName $hypervHost

$intOctFive = [Convert]::ToInt32(($vsmssd.MaximumMacAddress.Substring(8,2)),16)

If ($intOctFive -lt 254) {
    $newIntOct = Get-Random -minimum ($intOctFive + 1) -maximum 255
    $newStrOct = ([Convert]::ToString($newIntOct, 16)).ToUpper()
    $vsmssd.MaximumMacAddress = ($vsmssd.MaximumMacAddress.Substring(0,8) + $newStrOct + $vsmssd.MaximumMacAddress.Substring(10,2))
}
Else {
    $intOctFour = [Convert]::ToInt32(($vsmssd.MaximumMacAddress.Substring(6,2)),16)
    $newIntOct = Get-Random -minimum ($intOctFour + 1) -maximum 255
    $newStrOct = ([Convert]::ToString($newIntOct, 16)).ToUpper()
    $vsmssd.MaximumMacAddress = ($vsmssd.MaximumMacAddress.Substring(0,6) + $newStrOct + $vsmssd.MaximumMacAddress.Substring(8,4))
}

$VMManagementService.ModifyServiceSettings($vsmssd.psbase.gettext(1))

Modifying Hyper-V Server settings with WMI

Here is a little thing that I recently put together to modify Server level settings of my Hyper-V Server.  Most of my focus so far has been on the VMs themselves, why not tweak the server?

Why might you need to change settings of the Hyper-V server itself?  Um, because you can, and therefore you should.  You might need to change the MAC address range, or the default path, or some other crazy thing.

What can you change?

Well, first, you have to get the Msvm_VirtualSystemManagementServiceSettingData WMI object.  This class is the settings of the hypervisor and things that the management OS must keep in check.

If you get the WMI object and then type it out in the PowerShell windows you will see something like this:


__GENUS                    : 2
__CLASS                    : Msvm_VirtualSystemManagementServiceSettingData
__SUPERCLASS               : CIM_SettingData
__DYNASTY                  : CIM_ManagedElement
__RELPATH                  : Msvm_VirtualSystemManagementServiceSettingData.InstanceID="Microsoft:BJEDAR2"
__PROPERTY_COUNT           : 13
__DERIVATION               : {CIM_SettingData, CIM_ManagedElement}
__SERVER                   : BJEDAR2
__NAMESPACE                : root\virtualization
__PATH                     : \\BJEDAR2\root\virtualization:Msvm_VirtualSystemManagementServiceSettingData.InstanceID="Microsoft:BJEDAR2"
BiosLockString             :
Caption                    : Hyper-V Virtual System Management Service
DefaultExternalDataRoot    : C:\Users\Public\VMs\
DefaultVirtualHardDiskPath : C:\Users\Public\VMs\
Description                : Settings for the Virtual System Management Service
ElementName                : Hyper-V Virtual System Management Service
InstanceID                 : Microsoft:BJEDAR2
MaximumMacAddress          : 00155D748DFF
MinimumMacAddress          : 00155D680A00
NumaSpanningEnabled        : True
PrimaryOwnerContact        :
PrimaryOwnerName           : Administrators
ScopeOfResidence           :

Now, how can I do something useful with this?  Read on.  I am going to modify the MAC address range of the host.

First, you have to get an instance of the Virtual Machine Management Service as this will give you the method you need to actually modify the WMI objects.  If you don’t do this, you only have a bunch of read only properties through CIM.

$VMManagementService = Get-WmiObject -Namespace root\virtualization -Class Msvm_VirtualSystemManagementService -ComputerName $hypervHost

Now, I want to get the object I typed out above:

$vsmssd = Get-WmiObject Msvm_VirtualSystemManagementServiceSettingData -Namespace "root\virtualization" -ComputerName $hypervHost

And see the MAC address range:

$vsmssd.MinimumMacAddress
$vsmssd.MaximumMacAddress

Then change the value:

$vsmssd.MaximumMacAddress = “00155D74FFFF”

Then put the change:

$VMManagementService.ModifyServiceSettings($vsmssd.psbase.gettext(1))

A return value of “0” is success, a return of “4096” is generally a long running job or something went bad.

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

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