Как проимпортировать неимпортируемое

  • Tutorial

Проблема, идея, и решение


Здравствуйте, дорогие мои детишечки. Спешу сообщить вам, что в мою голову пришла еще одна идея, которая вылилась вот в эту заметку. Идея, собственно говоря, пришла из проблемы, которую подкинула горячо мной любимая и уважаемая компания Microsoft и их новый продукт Windows Server 2012 R2. И тут я нисколько не иронизирую, мне они действительно нравятся. Но начнем по порядку.
Прежде всего отмечу, что я, кроме всего прочего еще и тренер по всякого рода продуктам Microsoft, и соответственно имею доступ к определенным плюшкам в виде готовых виртуальных машин для подготовки к курсам, в рамках учебного центра. И вот, собственно, решил я попробовать погонять новый сервер, ну и, как водится, развернуть на нем виртуалочки от одного курса. Выкачал эти машины, все подготовил, распаковал. И тут меня поджидало ужасное. Они категорически отказывались импортироваться.
image
В общем, оказалось, что машины экспортированы на Windows Server 2008 и в Windows 2012 R2 импортироваться не будут. Не поддерживается это по определенным техническим причинам.
Что же делать, как они могли, спросите вы, и будете правы. В моем случае у меня не было под рукой Windows Server 2008 и я стал искать альтернативный вариант. В общем и целом он прост. В одном из подкаталогов экспортированной машины нашелся файл с именем вида {GUID}.exp. Он представляет собой конфигурацию экспортированной виртуальной машины. Именно из-за него она не импортируется, и мы это собираемся изменить. Я решил просто взять нужные мне настройки из этого файла, привести их к подходящему виду и просто создать новые виртуальные машины с теми же настройками, что и исходные. Чтобы долго не заморачиваться, я решил выбрать из файла имя машины, пути к файлам VHD, конфигурацию памяти и имя виртуальной сети, к которой эти машинки должны подключаться. Но не делать же это руками, верно. Тем более если открыть этот файл и посмотреть на его содержимое, то волосы на голове встают дыбом и пропадает желание искать что-то в нем вручную. А если их больше одного. В общем решено, пишем скрипт

Скрипт

На чем пишем? Конечно, на старом добром powershell 4, который поставляется в комплекте с новым сервером и WIndows 8.1. С чего начнем? А начнем сразу в лоб, а как же иначе. Открываем файл, благо есть тип [xml] который упрощает ковыряние во внутренностях и дебрях экспортированной конфигурации. Вкратце, файлик этот содержит кучу WMI классов со значениями свойств. Содержимое этих классов выгружено в XML и записано в файл. Поскольку я не сильно знаком с этими WMI классами, та и с XLM тоже, пришлось помучаться, добывая эти параметры в лоб. Вот что вышло:

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 =========="
}


И это сработало. Но глядя на все это, и вспоминая не несколько часов, которые я потратил на поиск нужных частей текста я понял что все это ужасно. Мне тут же вспомнился комментарий камрада Pinsky о паралимпийских играх по программированию. А что, я ж не программист, таки. Все равно ведь работает. Но хотелось чего-то большего, более краткого, красивого и лаконичного. В общем, тут я вспомнил знакомое слово XPATH. Честно говоря, до этого момента, о самой технологии кроме самого слова, я ничего не знал. Я подозревал, что эта штука должна делать но пользоваться не приходилось. Я подумал, что стоило бы попробовать. Как это счастье работает с powershell и работает ли. Пара часов прошли в поисках по гуглу и тестах. И вот оно, почти счастье:

[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


Вот такая вот штука. Значительно короче, приятней читать, понятней. И еще и работает.

PS. К слову стоит отметить, что в конфигурациях машины были неправильно указаны пути к самим файлам VHD. То есть самораспаковывающийся архив складывает файлы в каталог [..]\1234В-XX-YY1\[..]\file.vhd а в конфигурации они были совсем другие [..]\1234А-XX-YY1\[..]\file.vhd. На то чтобы разглядеть эту разницу вместо того чтобы биться головой о стену в поисхах ошибки в скрипте ушло около часа с копейками.
Метки:
  • –6
  • 2,8k
  • 2
Поделиться публикацией
Похожие публикации
Комментарии 2
  • +3
    «Как проимпортировать неимпортируемое» — детишки, признаемся честно, мы ведь не догадались что речь пойдет о переносе настроек виртуальной машины из одной версии Windows Server на другую? Теперь вопрос: так и было задумано?
    Это я к трем словам придрался. А все сообщение до хабраката не более информативно. Мне пришлось открывать эту воду, что бы понять что оно не нужно. Ну и теги конечно.
    • 0
      Пара пояснений для тех, кто писатель, я не читатель

      Про детишечек www.youtube.com/watch?v=pcLShHlyMVM

      Про статью. В двух словах, есть всего два способа запустить машину из Windows 2008 на Windows 2012 R2. Первый, это скопировать ее целиком, без импорта. Второй -создать заново с настройками старой. Но что делать, если у вас их нет, этих настроек. Или таких машин у вас 100500? Выше есть скрипт, или даже 2.
      Правда тот кто не сталкивался, вряд ли оценит всю глубину сложившейся проблемы )

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.