Problem, idea, and solution
Hello, my dear kids. I hasten to inform you that another idea came into my head, which resulted in this note. The idea, in fact, came from a problem that was thrown by my beloved and respected Microsoft company, which was dear to me, and their new product Windows Server 2012 R2. 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 trainer 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 virtual computers on it from one course. Pumped these cars, all prepared, unpacked. And then I was waiting for a terrible. They categorically refused to be imported.

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, how could they, you ask, and you will be right. In my case, I did not have Windows Server 2008 at hand and I began to look for an alternative option. In general, it is simple. In one of the subdirectories of the exported machine there was a file with the name of the form {GUID} .exp. It is a configuration of the exported virtual machine. It is because of it that it is not imported, and we are going to change it. I decided to just take the settings I needed from this file, bring them to the appropriate form and just create new virtual machines with the same settings as the original ones. In order not to bother for a long time, I decided to choose from the file the name of the machine, the paths to the VHD files, the memory configuration and the name of the virtual network to which these machines should be connected. But don't do it with your hands, right. Especially if you open this file and look at its contents, then the hair on the head stand on end and the desire to search for something in it is lost. And if there are more than one. In general, we decided to write a script.
Script
What do we write 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 in the forehead, but how else. Open the file, the benefit is the type [xml] which simplifies picking in the insides 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 uploaded to XML and written to a file. Since I am not very familiar with these WMI classes, the XLM also had to suffer, getting these parameters in the forehead. This is what happened:
cls $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 {$_.property | 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["$($_.name)"]=$_.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["$($_.name)"]=$_.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["$($_.name)"]=$_.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["$($_.name)"]=$_.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 #$vmObj.Disks.Path [int64]$vmMemoryReservation = [int64]$vmObj.Memory.Reservation * 1MB [int64]$vmMemoryLimit = [int64]$vmObj.Memory.Limit * 1MB $vmNetwork = $vmObj.Network.ElementName $vmName $vmObj.Disks.Path $vmMemoryReservation $vmMemoryLimit $vmNetwork #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 not remembering the few hours I spent searching for the right parts of the text, I realized that all this was terrible. I immediately remembered the comrade
Pinsky 's
comment on the Paralympic programming games. Well, I'm not a programmer, after all. It still works. But I wanted something more, more concise, beautiful and concise. In general, here I remembered the familiar word XPATH. Honestly, until that moment, I did not know anything about the technology itself except for the word itself. I suspected that this thing should be done, but it was not necessary to use it. I thought it would be worth a try. How this happiness works with powershell and whether it works. A couple of hours went 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["$($_.name)"]=$_.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["$($_.name)"]=$_.value} {new-object psobject -prop $obj} | select Limit,Reservation #class 'Msvm_SwitchPort' $network = ($vm.SelectNodes("//INSTANCE[@CLASSNAME='Msvm_SwitchPort']/PROPERTY") | % {$obj=@{}} {$obj["$($_.name)"]=$_.value} {new-object psobject -prop $obj}).ElementName $vmName $hardDrives $memory $network
Here is such a thing. Much shorter, more pleasant to read, more understandable. And also works.
Ps. By the way, it is worth noting that the paths to the VHD files themselves were incorrectly indicated in the machine configurations. That is, the self-extracting archive puts the files in the [..] \ 1234 directory
in -XX-YY1 \ [..] \ file.vhd and in the configuration they were completely different [..] \ 1234
A -XX-YY1 \ [.. ] \ file.vhd. It took about an hour with a penny to see this difference instead of banging your head against the wall in search of an error in the script.