Published: Dec 18, 2013
The light at the end of the tunnel is now getting very bright. Our tenants are registered and the clouds have formed nicely, in this final post in our multi-part series on the migration efforts to get us to System Center Virtual Machine Manager 2012 R2 (SCVMM 2012 R2), we will delegate our new clouds to the tenants. Once the clouds have been shared we will re-establish the quota on these, then finally we need we will assign all the VMs back to their respective clouds and tenants.
Wrapping up, we will then address the VM templates, migrating these to the target environment (which in reality was a lot more difficult than I had initially expected), and finally delegating permissions to the templates enabling our tenants to once more deploy infrastructure, hosted on our fresh new SCVMM 2012 R2 environment.
(Editor’s note: Need to catch up? In part one, we upgraded to System Center Virtual Machine Manager 2012 R2. In part two, I provided an overview of migrating to SCVMM. In part three, we looked at how to migrate the hosts and library.)
In an earlier step we exported a list of user roles from the source environment, we will leverage this export to get the clouds which are delegated to each tenant, as that information was contained in the export.
On the target, the following script will reference the earlier generated UserRoles.csv file, and then loop trough adding all the clouds which are assigned to each user role.
$Tenants = Import-Csv .\UserRoles.csv foreach ($Role in $Tenants) { $parentUserRole = Get-SCUserRole -Name $Role.ParentUserRole $userRole = Get-SCUserRole -Name $Role.Name $CloudList = $null $CloudList = @() if ( $Role.Cloud0 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud0 } if ( $Role.Cloud1 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud1 } if ( $Role.Cloud2 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud2 } if ( $Role.Cloud3 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud3 } if ( $Role.Cloud4 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud4 } if ( $Role.Cloud5 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud5 } if ( $Role.Cloud6 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud6 } if ( $Role.Cloud7 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud7 } if ( $Role.Cloud8 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud8 } if ( $Role.Cloud9 -ne "") { $CloudList += Get-SCCloud -Name $Role.Cloud9 } Set-SCUserRole -UserRole $parentUserRole -AddScope $CloudList Set-SCUserRole -UserRole $userRole -AddScope $CloudList }
Keeping our focus on the clouds, we will now proceed to export the quota details that were assigned to the clouds in the source environment. Place this detail into another CSV file, which you can edit if needed and then move forward by reapplying the details to the target environment.
As this point the scripts are starting to look quite similar. This time we will grab the quotas for each, and using the loop, and post these into the CSV file for us.
$ExportedRoleQuotas = @() $Tenants = Get-SCUserRole | ?{$_.Profile -eq "SelfServiceUser"} foreach ($Role in $Tenants) { $RoleID = Get-SCUserRole -Name $Role.Name $UserRoleQuota = Get-SCUserRoleQuota -UserRole $RoleID foreach ($Quota in $UserRoleQuota) { $RoleName = $Role.Name $CloudName = Get-SCCloud -ID $Quota.CloudID $QuotaPerUser = $Quota.QuotaPerUser $UseCustomQuotaCountDefault = $Quota.UseCustomQuotaCountDefault $UseCPUCountDefault = $Quota.UseCPUCountDefault $UseVMCountDefault = $Quota.UseVMCountDefault $UseMemoryDefault = $Quota.UseMemoryDefault $UseStorageGBDefault = $Quota.UseStorageGBDefault $CustomQuotaCount = $Quota.CustomQuotaCount $CPUCount = $Quota.CPUCount $VMCount = $Quota.VMCount $MemoryMB = $Quota.MemoryMB $StorageGB = $Quota.StorageGB $ExportedRoleQuotas += "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12}" -f $RoleName, $CloudName, $QuotaPerUser, $UseCustomQuotaCountDefault, $UseCPUCountDefault, $UseVMCountDefault, $UseMemoryDefault, $UseStorageGBDefault, $CustomQuotaCount, $CPUCount, $VMCount, $MemoryMB, $StorageGB } } $ExportedRoleQuotas | Export-Csv UserRoleQuotas.csv
On the target side we need to be a little smarter, as there are two different quotas by default on each role – per-user and shared – so the import script needs to figure out with an IF statement which of these we are updating. After that we will either set the quota as unlimited or defined (but not both), so we will use more IF statements to get this correct.
$UserRoleQuotas = Import-Csv .\UserRoleQuotas.csv foreach ($Quota in $UserRoleQuotas) { $JobGUID = ([System.Guid]::NewGuid()) $jobID = $JobGUID.Guid $CloudName = Get-SCCloud -Name $Quota.CloudName if ($Quota.QuotaPerUser -eq "True") { if ( $Quota.UseCustomQuotaCountDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser –UseCustomQuotaCountMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser -CustomQuotaCount $Quota.CustomQuotaCount } if ( $Quota.UseCPUCountDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser –UseCPUCountMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser -CPUCount $Quota.CPUCount } if ( $Quota.UseVMCountDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser –UseVMCountMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser -VMCount $Quota.VMCount } if ( $Quota.UseMemoryDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser –UseMemoryMBMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser -MemoryMB $Quota.MemoryMB } if ( $Quota.UseStorageGBDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser –UseStorageGBMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -QuotaPerUser -StorageGB $Quota.StorageGB } } else { if ( $Quota.UseCustomQuotaCountDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID –UseCustomQuotaCountMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -CustomQuotaCount $Quota.CustomQuotaCount } if ( $Quota.UseCPUCountDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID –UseCPUCountMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -CPUCount $Quota.CPUCount } if ( $Quota.UseVMCountDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID –UseVMCountMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -VMCount $Quota.VMCount } if ( $Quota.UseMemoryDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID –UseMemoryMBMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -MemoryMB $Quota.MemoryMB } if ( $Quota.UseStorageGBDefault -eq "True") { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID –UseStorageGBMaximum } else { Set-SCUserRoleQuota -Cloud $CloudName -JobGroup $JobID -StorageGB $Quota.StorageGB } } $userRole = Get-SCUserRole -Name $Quota.RoleName Add-SCUserRolePermission -Cloud $CloudName -JobGroup $JobID Set-SCUserRole -UserRole $userRole -JobGroup $JobID }
Milestone time now: We are finally ready to start giving the virtual machines back to their respective owners. As with the earlier steps, we need to export all the metadata from the source environment, place all this into a CSV file, and then take all this rich information and use it on our target environment to assign the updates to our machines in our target environment. As with all of our scripts so far, this exercise will should begin to feel quite comfortable, so we just need to make the necessary adjustments to gather the relevant information.
This time we will create a CSV file called VMs.cvs, which will contain some of the basic details, such as operating system, quota points, user roles, clouds, owners, and so on. What I don’t export is stuff like custom data, etc., but as you can figure out, it is not to difficult to extend these functions to get as much or as little as you like!
$ExportedVMs = @() $VirtualMachines = Get-SCVirtualMachine foreach ($VM in $VirtualMachines) { $ExportedVMs += $VM | select Name, Cloud, SelfServiceUserRole, OperatingSystem, QuotaPoint, UserRole, Owner } $ExportedVMs | Export-Csv VMs.csv
Back on the target environment, we will go ahead and work the CSV file as we determine the name of the VM which needs to be assigned. Then, using the information in the export, we simply use the Set-SCVirtualMachine command to apply the configuration setting.
$VirtualMachines = Import-Csv .\VMs.csv foreach ($VM in $VirtualMachines) { # Clean Up Null Fields if ($VM.Description -eq $null) { $Description = "" } else { $Description = $VM.Description} $MyVM = Get-SCVirtualMachine -Name $VM.Name $OperatingSystem = Get-SCOperatingSystem | where {$_.Name -eq $VM.OperatingSystem} $UserRole = Get-SCUserRole -Name $VM.UserRole $Cloud = Get-SCCloud | where {$_.Name -eq $VM.Cloud} $JobGUID = ([System.Guid]::NewGuid()) $jobID = $JobGUID.Guid $MyVMName = $VM.Name write-host "Applying Cloud and Role for $MyVMName" Set-SCVirtualMachine -VM $MyVM -Owner $VM.Owner -UserRole $UserRole -QuotaPoint $VM.QuotaPoint -OperatingSystem $OperatingSystem -Cloud $Cloud -JobGroup $JobID -RunAsynchronously }
With the launch of SCVMM 2012, Microsoft enabled the ability to export library resources to XML files with the use of a simple console wizard. This wizard is available when you change to the Library view and select the VM Templates node in the navigation tree.
Once in this view, you can select all the templates presented, then from the Actions group on the ribbon select Export.
A new wizard will be presented, listing all the templates we just selected for exporting. We can choose the option Export any sensitive template settings to keep things like product keys and passwords in the exported data. I don’t recommend enabling the option to Encrypt the XML files, as we are going to need to do some work on these to migrate them to the target environment.
Simply specific the path to where you wish to have all the XML files exported (one per template) and click OK for VMM to being its procedure.
Importing these templates can also be accomplished via the console on the destination server, but you are restricted to one at a time, which is no fun if you have 400 templates to address. Every template will also require that you attempt to map the virtual hard disk that is referenced in the template to an equivalent hard disk, which is now registered in your new library. Similar work will be needed also from some VM/logical networks.
As this procedure is so slow, and as the library we are referencing in our migration target is actually the same library that the source was using, we can save some time since the resource names should be unchanged.
The following script (created by my colleague Oscar Mazanares – thank you!) will enumerate all the XML files. Check to see what mappings are required and address these by using essentially the same resources from our new environment before finally completing the import process and moving any successfully processed XML files to a new folder. It does not cover 100 percent of the scenarios, but it addresses all of our requirements, including VHDs, vFloppies, and networks.
param ( [string]$source=$(Throw "Parameter 'source' cannot be empty"), [string]$filename="", [switch]$overwrite=$false )
Lets begin by enumerating all the exported templates which have been placed in the folder that we will reference as $Source.
Write-Debug "Getting list of Exported Templates files from $source..." if ($filename -eq "") { $templatefiles = Get-ChildItem $source | where { $_.psIsContainer -eq $false } } else { $templatefiles = Get-ChildItem $source | where { $_.psIsContainer -eq $false -and $_.Name -eq "$filename" } }
We need to create a holder, which we will use to ensure that we treat the template in the correct format.
$xml = New-Object XML
As we need to locate the virtual hard disks, floppies, and ISO files, which are referenced in the XML, the next three functions will be used as helpers to scan the library locations and send back a reference to the files when located so that we can mark-up the XML with the correct information as we prepare to import into VMM.
Function GetDisk($disk) { Write-Debug "`t- Searching for Virtual Disk" $d = Get-SCVirtualHardDisk | where { $_.Name -eq "$disk" } if ($d -ne $null) { if ($d.Count -ne 1) { Write-Debug ([String]::Concat("`t- ERROR: Found ", $d.Count, " instances of disk '", $disk, "'!")) $null } else { Write-Debug "`t- Found!" $d } } else { $null } } Function GetISO($iso) { Write-Debug "`t- Searching for ISO" $d = Get-SCISO | where { $_.Name -eq "$iso" } if ($d -ne $null) { if ($d.Count -ne 1) { Write-Debug ([String]::Concat("`t- ERROR: Found ", $d.Count, " instances of iso '", $iso, "'!")) $null } else { Write-Debug "`t- Found!" $d } } else { $null } } Function GetFloppy($floppy) { Write-Debug "`t- Searching for Floppy" $d = Get-SCVirtualFloppyDisk | where { $_.Name -eq "$floppy" } if ($d -ne $null) { if ($d.Count -ne 1) { Write-Debug ([String]::Concat("`t- ERROR: Found ", $d.Count, " instances of floppy '", $floppy, "'!")) $null } else { Write-Debug "`t- Found!" $d } } else { $null } }
As an example of the flexibility we have available, we will create a reference to a VM network, and each time we find a specific network in the source XML template, we can easily replace it with the new reference, ensuring that during the migration we can easily modify the templates to match our target environment.
$vlan202 = Get-SCVMNetwork -Name "VLAN202" $templatefiles | % { # We will set the name of the current XML template, and open it up to inspect it $templatefile=$_.Name $templatefilefullpath=$source + "\" + $templatefile $xml.Load($templatefilefullpath) $templatename=$xml.Envelope.VirtualSystem.Name "Importing template $templatename (XML=$templatefile)"
Next, use the VMM command to determine what resources are referenced in the template. Using this we will be able to check and fix any mappings necessary.
$package = Get-SCTemplatePackage -Path $templatefilefullpath if ($?) { $allMappings = New-SCPackageMapping -TemplatePackage $package # At this point $allMappings contains a list of the mappings which we need to re-associate if ($?) { $remapOK = $true $allMappings | ForEach-Object { # We will no loop, as we process each mapping in turn $mapping = $_ if ($mapping.ReferencedObject -eq $null) { $packId=$mapping.PackageId $mappingType = $mapping.Type $resource = $null Write-Debug "Checking $mappingType '$packId'" switch ($mappingType) { "VMNetwork" {
See if the template source was connected to a network called VLAN3000. If so, replace it with the reference to our newer VLAN202. Otherwise, as the networks are the same, we will leave the auto-mapping alone.
if ($mapping.PackageName.StartsWith("VLAN3000")) { Write-Debug "`t- VLAN to be replaced by $vlan202" $resource = $vlan202 } } "VirtualHardDisk" {
Using the reference from the mapping, we will use the helper function to locate the disk on our new libraries and update the mapping. This works since we did not rename any disk or other resources in the migration.
$resource = GetDisk($packId) } "ISO" { $resource = GetISO($packId) } "VirtualFloppyDisk" { $resource = GetFloppy($packId) } } if ($resource -ne $null) { Write-Debug "`t- Changing resource $resource" $r=Set-SCPackageMapping -PackageMapping $mapping -TargetObject $resource if (!$r) { "$templatefile # ERROR: An error has occurred running Set-SCPackageMapping" $remapOK = $false } } else { "$templatefile # ERROR: Cannot map object '$packId' (TYPE=$mappingType)" $remapOK = $false } } } if ($remapOK) { if ($overwrite) { $r=Import-SCTemplate -TemplatePackage $package -Name "$templatename" -PackageMapping $allMappings -SettingsIncludePrivate –overwrite } else { $r=Import-SCTemplate -TemplatePackage $package -Name "$templatename" -PackageMapping $allMappings –SettingsIncludePrivate } if ($r) { Move-Item $templatefilefullpath $source\done "Completed!" } else { "$templatefile # ERROR: An error has occurred running Import-SCTemplate" } } } else { "$templatefile # ERROR: An error has occurred running New-SCPackageMapping" } } else { "$templatefile # ERROR: An error has occurred running Get-SCTemplatePackage" } "" }
We are about to implement the last stage of our migration (please don’t get too emotional). That is to take all the resources we just imported and assign them back to the tenants to which they are to be utilized. This last procedure is going to be an easy one, as it’s using the same approach we have used in a number of the steps previously, including exporting all the metadata to a CSV file, looping trough all this good detail, and reapplying the permissions to the templates on the target environment
We do need a new CSV file for this one, which i have called VMTemplateDelegations.csv. This as you can now guess has the name of the template, the role which is it owned by, and the tenants whom are allowed to use the template. The loop is quite simple, and again it can be extended to export more details if needed, but this should address the needs of our migration keeping things lean and clean.
$ExportedTemplateDelegations = @() $VMTemplates = Get-SCVMTemplate foreach ($Template in $VMTemplates) { $ExportedTemplateDelegations += $Template | select Name, UserRole, @{Name='GrantedToList0';Expression={$_.GrantedToList[0].name}}, @{Name='GrantedToList1';Expression={$_.GrantedToList[1].name}}, @{Name='GrantedToList2';Expression={$_.GrantedToList[2].name}}, @{Name='GrantedToList3';Expression={$_.GrantedToList[3].name}}, @{Name='GrantedToList4';Expression={$_.GrantedToList[4].name}}, @{Name='GrantedToList5';Expression={$_.GrantedToList[5].name}}, @{Name='GrantedToList6';Expression={$_.GrantedToList[6].name}}, @{Name='GrantedToList7';Expression={$_.GrantedToList[7].name}}, @{Name='GrantedToList8';Expression={$_.GrantedToList[8].name}}, @{Name='GrantedToList9';Expression={$_.GrantedToList[9].name}}, @{Name='GrantedToList10';Expression={$_.GrantedToList[10].name}}, @{Name='GrantedToList11';Expression={$_.GrantedToList[11].name}}, @{Name='GrantedToList12';Expression={$_.GrantedToList[12].name}}, @{Name='GrantedToList13';Expression={$_.GrantedToList[13].name}}, @{Name='GrantedToList14';Expression={$_.GrantedToList[14].name}}, @{Name='GrantedToList15';Expression={$_.GrantedToList[15].name}}, @{Name='GrantedToList16';Expression={$_.GrantedToList[16].name}} } $ExportedTemplateDelegations | Export-Csv VMTemplateDelegations.csv
For the final time on our target server we are pulling out the ever reliable loop one last time. At this point we need no additional narrative – just a short comment to confirm that we are reusing the Grant-SCResource command to make sure that the relevant tenant grabs access to the templates we imported just a short while ago.
$VMTemplates = Import-Csv .\VMTemplateDelegations.csv foreach ($Template in $VMTemplates) { $GrantedToList = $null $GrantedToList = @() if ($Template.GrantedToList0 -ne "") { $GrantedToList += $Template.GrantedToList0 } if ($Template.GrantedToList1 -ne "") { $GrantedToList += $Template.GrantedToList1 } if ($Template.GrantedToList2 -ne "") { $GrantedToList += $Template.GrantedToList2 } if ($Template.GrantedToList3 -ne "") { $GrantedToList += $Template.GrantedToList3 } if ($Template.GrantedToList4 -ne "") { $GrantedToList += $Template.GrantedToList4 } if ($Template.GrantedToList5 -ne "") { $GrantedToList += $Template.GrantedToList5 } if ($Template.GrantedToList6 -ne "") { $GrantedToList += $Template.GrantedToList6 } if ($Template.GrantedToList7 -ne "") { $GrantedToList += $Template.GrantedToList7 } if ($Template.GrantedToList8 -ne "") { $GrantedToList += $Template.GrantedToList8 } if ($Template.GrantedToList9 -ne "") { $GrantedToList += $Template.GrantedToList9 } if ($Template.GrantedToList10 -ne "") { $GrantedToList += $Template.GrantedToList10 } if ($Template.GrantedToList11 -ne "") { $GrantedToList += $Template.GrantedToList11 } if ($Template.GrantedToList12 -ne "") { $GrantedToList += $Template.GrantedToList12 } if ($Template.GrantedToList13 -ne "") { $GrantedToList += $Template.GrantedToList13 } if ($Template.GrantedToList14 -ne "") { $GrantedToList += $Template.GrantedToList14 } if ($Template.GrantedToList15 -ne "") { $GrantedToList += $Template.GrantedToList15 } if ($Template.GrantedToList16 -ne "") { $GrantedToList += $Template.GrantedToList16 } $TemplateName = Get-SCVMTemplate | where {$_.Name -eq $Template.Name} Write-host (TemplateName.Name) Grant-SCResource -Resource $TemplateName -UserRoleName $GrantedToList }
Well, that was 18 steps of pain or fun, depending on how you look at this. However, one thing is for sure: You now have an environment that should be clean and agile, offering you the control you need to pick and choose what makes the cut and what hits the trash.
I hope you have found this series useful. If I can offer any more support, hit up the comments. But whatever you do, remember to plan well and sleep even better.