How to import non-importable

  • Tutorial

Problem, Idea, and Solution

Hello my dear kids. I hasten to inform you that another idea came into my head that resulted in this note. The idea, in fact, came from a problem that my beloved and respected Microsoft company and their new product, Windows Server 2012 R2, threw up with me. And here I am not at all ironic, I really like them. But let's start in order.
First of all, I note that, among other things, I am also a coach for all kinds of Microsoft products, and accordingly I have access to certain buns in the form of ready-made virtual machines for preparing for courses, as part of the training center. And so, in fact, I decided to try to drive a new server, and, as usual, deploy virtuala from one course on it. I deflated these machines, prepared everything, unpacked it. And then awful awaited me. They categorically refused to import.
In general, it turned out that the machines were exported to Windows Server 2008 and would not be imported to Windows 2012 R2. This is not supported for certain technical reasons.
What to do as they could, you ask, and you will be right. In my case, I did not have at hand Windows Server 2008 and I began to look for an alternative. In general, it is simple. In one of the subdirectories of the exported machine, a file was found with the name of the form {GUID} .exp. It represents the configuration of the exported virtual machine. It is because of him that it is not imported, and we are going to change this. I decided to just take the settings I need from this file, bring them to the right form, and simply create new virtual machines with the same settings as the original ones. In order not to bother for a long time, I decided to select the machine name from the file, the paths to the VHD files, the memory configuration and the name of the virtual network to which these machines should connect. But do not do it with your hands, right. Especially if you open this file and look at its contents, then the hair on the head stands on end and the desire to search for something in it manually disappears. And if there are more than one. In general, we decided to write a script


What are we writing on? Of course, on the good old powershell 4, which comes bundled with the new server and WIndows 8.1. Where do we start? And let's start right on the forehead, but how could it be otherwise. We open the file, since there is a type [xml] that simplifies picking in the guts and wilds of the exported configuration. In short, this file contains a bunch of WMI classes with property values. The contents of these classes are unloaded in XML and written to a file. Since I am not very familiar with these WMI classes, the one with XLM too, I had to torment myself by mining these parameters in the forehead. Here's what happened:

$tmp = dir "C:\Program Files\Microsoft Learning\20413\*\*.exp" -Recurse
$tmp | % {
    # read file
    [xml]$vm = gc $_.fullname
    # parsing of the various of different internal XML structures using "properties" notation
    # CLASSNAME Msvm_VirtualSystemGlobalSettingData
    $disks = ($vm.DECLARATIONS.DECLGROUP.'VALUE.OBJECT'.instance | where classname -like "*resource*") | 
                    where {$ | where name -like "*units*" | 
                    where value -eq "disks"}
    $newVM = @{}
    # CLASSNAME Msvm_VirtualSystemGlobalSettingData
    $newVM.Global = $vm.DECLARATIONS.DECLGROUP.'VALUE.OBJECT'.instance | 
                                where classname -like "*Msvm_VirtualSystemGlobalSettingData*" | 
                                select -ExpandProperty property |  
                                # below passage is most exciting
                                % {$obj=@{}} {$obj["$($"]=$_.value} {new-object psobject -prop $obj}
    # disks configuration contains some internal nodes, extractiong them to get the paths to VHDs
    $newVM.Disks = $disks | % { $prop = @{}; $disk = $_; $disk | select -ExpandProperty property | 	
                       %  {$obj=@{}} {$obj["$($"]=$_.value}; 
		                  $obj."Path" = ($disk | select -expand property.array)."value.array".value; 
		                  New-object psobject -prop $obj}
    # CLASSNAME Msvm_MemorySettingData
    $newVM.Memory = $vm.DECLARATIONS.DECLGROUP.'VALUE.OBJECT'.instance | 
                              where classname -like "*memory*" | select -ExpandProperty property |  
                              % {$obj=@{}} {$obj["$($"]=$_.value} {new-object psobject -prop $obj}
    # CLASSNAME Msvm_SwitchPort
    $newVM.Network = $vm.DECLARATIONS.DECLGROUP.'VALUE.OBJECT'.instance | 
                               where classname -like "*switch*" | select -ExpandProperty property | 
                               % {$obj=@{}} {$obj["$($"]=$_.value} {new-object psobject -prop $obj}
    # as far as $newVM is a hashtable, making an object from it
    $vmObj = New-object psobject -prop $newVM
    # variables, just to see what we've got
    $vmName = $vmObj.Global.ElementName
    [int64]$vmMemoryReservation = [int64]$vmObj.Memory.Reservation * 1MB
    [int64]$vmMemoryLimit =  [int64]$vmObj.Memory.Limit * 1MB
    $vmNetwork = $vmObj.Network.ElementName
    #actual import
    New-VM -Name $vmName -MemoryStartupBytes $vmMemoryLimit #-VHDPath $vmObj.Disks.Path[0]
    $vmObj.Disks.Path | % {Add-VMHardDiskDrive -VMName $vmName -Path $_}
    Set-VMMemory -VMName $vmName -MaximumBytes $vmMemoryLimit -DynamicMemoryEnabled $true
    Get-vm -Name $vmName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $vmNetwork
    checkpoint-vm -Name $vmName
    "========== $vmName =========="

And it worked. But looking at all this, and remembering not the few hours that I spent searching for the right parts of the text, I realized that all this is terrible. I immediately recalled the comrade Pinsky 's comment on Paralympic programming games. Well, I'm not a programmer, though. It works anyway. But I wanted something more, more concise, beautiful and concise. In general, here I remembered the familiar word XPATH. Honestly, until this moment, I knew nothing about the technology itself except the word itself. I suspected that this thing should do but did not have to be used. I thought it would be worth a try. How does this happiness work with powershell and whether it works. A couple of hours went by in search of Google and tests. And here it is, almost happiness:

[xml]$vm = gc $path
#class 'Msvm_VirtualSystemGlobalSettingData'
$vmName = ($vm.SelectNodes("//INSTANCE[@CLASSNAME='Msvm_VirtualSystemGlobalSettingData']/PROPERTY") |  
        % {$obj=@{}} {$obj["$($"]=$_.value} {new-object psobject -prop $obj}).elementname
#class 'Msvm_ResourceAllocationSettingData'
$hardDrives = $vm.SelectNodes("(//INSTANCE[@CLASSNAME='Msvm_ResourceAllocationSettingData'])/PROPERTY.ARRAY[@NAME='Connection']/VALUE.ARRAY").value
#class 'Msvm_MemorySettingData'
$memory = $vm.SelectNodes("//INSTANCE[@CLASSNAME='Msvm_MemorySettingData']/PROPERTY")  |  
        % {$obj=@{}} {$obj["$($"]=$_.value} {new-object psobject -prop $obj} | select Limit,Reservation
#class 'Msvm_SwitchPort'
$network = ($vm.SelectNodes("//INSTANCE[@CLASSNAME='Msvm_SwitchPort']/PROPERTY")  |  
        % {$obj=@{}} {$obj["$($"]=$_.value} {new-object psobject -prop $obj}).ElementName

Here is such a thing. Much shorter, more pleasant to read, more understandable. And it also works.

PS. By the way, it is worth noting that the paths to the VHD files themselves were incorrectly specified in the machine configurations. That is, the self-extracting archive puts the files in the directory [..] \ 1234 B -XX-YY1 \ [..] \ file.vhd and in the configuration they were completely different [..] \ 1234 A -XX-YY1 \ [.. ] \ file.vhd. It took about an hour and a penny to figure out this difference instead of banging your head against the wall in search of errors in the script.

Also popular now: