这个还原脚本有一个小前提,就是原虚拟机不能先删除,原因是为了简化操作和参数,脚本会从原来虚拟机的属性中读取资源组,虚拟网络,可用性集等属性,然后直接利用原虚拟机的某个时间点的快照在和原虚拟机同一子网下面创建出一台类似的虚拟机。新的虚拟机由于不能使用原来的网络接口和IP地址,所以会重新生成这些对象。
如果希望将原虚拟机删除,可以在这个脚本的基础上进行酌情修改,比如可以先记录原虚拟机的各种信息,然后在脚本中删除原虚拟机,新建虚拟机的时候使用原虚拟机的组件,加上这部分逻辑后就能做出类似 Hyper-V 上面的还原效果了,直接可以把原虚拟机和原磁盘都覆盖掉。
不过感觉这种逻辑比较激进一些,有兴趣的读者可以做一下。
还原逻辑也大部分借用了之前读取快照的逻辑,额外加入了 RestoreSnapshot 的逻辑和创建虚拟机的逻辑,脚本如下:
param( [Parameter(Mandatory = $true)] [string]$SubscriptionName, [Parameter(Mandatory = $true)] [string]$ResourceGroupName, [Parameter(Mandatory = $true)] [string]$OldVMName, [Parameter(Mandatory = $true)] [string]$NewVMName ) Function GetResourceNameFromResourceId($resourceId) { return $resourceId.Substring($resourceId.LastIndexOf('/') + 1); } Function GetResourcePropertyFromResourceId($resourceId, $propertyName) { $propertyName = $propertyName + "/"; $rgName = $resourceId.Substring($resourceId.IndexOf($propertyName) + $propertyName.Length); return $rgName.Substring(0, $rgName.IndexOf("/")); } Function PrepareBlobContext($vhdAbsoluteUri) { $StorageAccountName = $vhdAbsoluteUri.Substring(8, $vhdAbsoluteUri.IndexOf(".blob.core.chinacloudapi.cn") - 8); #length of "https://" is 8 $ContainerPathIndex = $vhdAbsoluteUri.IndexOf("blob.core.chinacloudapi.cn/") + 27; $ContainerName = $vhdAbsoluteUri.SubString($ContainerPathIndex, $vhdAbsoluteUri.IndexOf('/', $ContainerPathIndex) - $ContainerPathIndex); $BlobName = $vhdAbsoluteUri.SubString($vhdAbsoluteUri.LastIndexOf('/') + 1); $storageAccount = Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName; $storageContext = $storageAccount.Context; $blobClient = $storageContext.Context.StorageAccount.CreateCloudBlobClient(); $blobContainer = $blobClient.GetContainerReference($ContainerName); $blob = $blobContainer.GetBlockBlobReference($BlobName); return $blob; } Function RestoreSnapshot($snapshot, $snapshotUri, $newVmName) { $StorageAccountName = $snapshotUri.Substring(8, $snapshotUri.IndexOf(".blob.core.chinacloudapi.cn") - 8); #length of "https://" is 8 $ContainerPathIndex = $snapshotUri.IndexOf("blob.core.chinacloudapi.cn/") + 27; $ContainerName = $snapshotUri.SubString($ContainerPathIndex, $snapshotUri.IndexOf('/', $ContainerPathIndex) - $ContainerPathIndex); $BlobName = $snapshotUri.SubString($snapshotUri.LastIndexOf('/') + 1); $storageAccount = Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName; $storageContext = $storageAccount.Context; $blobClient = $storageContext.Context.StorageAccount.CreateCloudBlobClient(); $blobContainer = $blobClient.GetContainerReference($ContainerName); $newBlobName = "{0}-{1}" -f $newVmName, $BlobName; $blob = $blobContainer.GetBlobReference($newBlobName); [void]($blob.StartCopyFromBlob($snapshot.SnapshotQualifiedUri)); return $blob.Uri; } Function RestoreVM($rgName, $oldVmName, $newVmName) { $vm = Get-AzureRmVM -ResourceGroupName $rgName -Name $oldVmName -WarningAction Ignore; $osDiskUri = $vm.StorageProfile.OsDisk.Vhd.Uri; $blob = PrepareBlobContext $osDiskUri; $osDiskSnapshotList += @(GetSnapshotListByBlob $blob); $osDiskSnapshotPropertiesList += @(GetSnapshotPropertiesList $osDiskSnapshotList $blob); $dataDiskSnapshotLists = @(); $dataDiskSnapshotPropertiesLists = @(); foreach($dataDiskUri in $vm.StorageProfile.DataDisks.Vhd.Uri) { $blob = PrepareBlobContext $dataDiskUri; $dataDiskSnapshotList = @(GetSnapshotListByBlob $blob); $dataDiskSnapshotPropertiesList = @(GetSnapshotPropertiesList $dataDiskSnapshotList $blob); $dataDiskSnapshotLists += $null; $dataDiskSnapshotLists[$dataDiskSnapshotLists.Count - 1] = $dataDiskSnapshotList; $dataDiskSnapshotPropertiesLists += $null; $dataDiskSnapshotPropertiesLists[$dataDiskSnapshotPropertiesLists.Count - 1] = $dataDiskSnapshotPropertiesList; } $resultList = @(); $count = $osDiskSnapshotPropertiesList.Count; for ($i = 0; $i -lt $count; $i++) { $findMatch = $true; $result = @(); $osDiskSnapshotTime = $osDiskSnapshotPropertiesList[$i].SnapshotTime; $result += $i; foreach ($dataDiskSnapshotPropertiesList in $dataDiskSnapshotPropertiesLists) { $matchIndex = FindMatchSnapshot $osDiskSnapshotTime $dataDiskSnapshotPropertiesList; if ($matchIndex -eq -1) { $findMatch = $false; break; } else { $result += $matchIndex; } } if ($findMatch) { $resultList += $null; $resultList[$resultList.Count - 1] = $result; } } $resultCount = $resultList.Count; if ($resultCount -eq 0) { Write-Host "Virtual Machine has no snapshots." -ForegroundColor Yellow; return; } Write-Host "Virtual Machine has the following snapshots:" -ForegroundColor Yellow; Write-Host ("{0, -10}{1}" -f "Index", "Snapshot Time") -ForegroundColor Green; for ($i = 0; $i -lt $resultCount; $i++) { Write-Host ("{0, -10}{1}" -f ($i+1), $osDiskSnapshotPropertiesList[$resultList[$i][0]].SnapshotTime) -ForegroundColor Green; } $restoredOSDiskUri = $null; $restoredDataDiskUris = @(); $dataDiskCount = $dataDiskSnapshotLists.Count; $selection = $resultList[(Read-Host "Please select the snapshot you want to restore") - 1]; If ($PSCmdlet.ShouldContinue("Confirm?", "=== Confirm restore Opeartion ===")) { $restoredOSDiskUri = RestoreSnapshot $osDiskSnapshotList[$selection[0]] $osDiskSnapshotPropertiesList[$selection[0]].SnapshotUri.AbsoluteUri $newVmName; for ($i = 0; $i -lt $dataDiskCount; $i++) { $restoredDataDiskUri = RestoreSnapshot $dataDiskSnapshotLists[$i][$selection[$i+1]] $dataDiskSnapshotPropertiesLists[$i][$selection[$i+1]].SnapshotUri.AbsoluteUri $newVmName; $restoredDataDiskUris += $restoredDataDiskUri; } Write-Host "Done"; } else { Write-Host "Canceled"; } # create new VM with restored vhds $newVmSize = $vm.HardwareProfile.VmSize; $location = $vm.Location; $availabilitySetId = $vm.AvailabilitySetReference.Id; $oldNicId = ($vm.NetworkProfile.NetworkInterfaces | where {$_.Primary -eq $true}).Id; $oldNicName = GetResourceNameFromResourceId $oldNicId; $oldNic = Get-AzureRmNetworkInterface -Name $oldNicName -ResourceGroupName $rgName; $subnetId = $oldNic.IpConfigurations[0].Subnet.Id; $subnetName = GetResourceNameFromResourceId $subnetId; $vnetName = GetResourcePropertyFromResourceId $subnetId "virtualNetworks"; $vnet = Get-AzureRmVirtualNetwork -Name $vnetName -ResourceGroupName $rgName; $subnet01 = Get-AzureRmVirtualNetworkSubnetConfig -Name $subnetName -VirtualNetwork $vnet; $publicIP = New-AzureRmPublicIpAddress -Name ("{0}PIP" -f $newVmName) -ResourceGroupName $rgName -Location $location -AllocationMethod Dynamic -IpAddressVersion IPv4 –Force; $NIC = New-AzureRmNetworkInterface -Name ("{0}NIC" -f $newVmName) -ResourceGroupName $rgName -Location $location -SubnetId $subnet01.Id -PublicIpAddressId $publicIP.Id; $OSDiskName = $newVmName + "_OSDisk"; $vmconfig = $null; if ($availabilitySetId -eq $null) { $vmconfig = New-AzureRmVMConfig -VMName $newVmName -VMSize $newVmSize; } else { $vmconfig = New-AzureRmVMConfig -VMName $newVmName -VMSize $newVmSize -AvailabilitySetId $vm.AvailabilitySetReference.Id; } if ($vm.StorageProfile.OsDisk.OsType.ToString() -eq "Windows") { $vmconfig = $vmconfig | Set-AzureRmVMOSDisk –Name $OSDiskName -VhdUri $restoredOSDiskUri -CreateOption attach -Windows; } else { $vmconfig = $vmconfig | Set-AzureRmVMOSDisk –Name $OSDiskName -VhdUri $restoredOSDiskUri -CreateOption attach -Linux; } $vmconfig = $vmconfig | Add-AzureRmVMNetworkInterface -Id $NIC.Id -Primary; $lun = 0; foreach ($restoredDataDiskUri in $restoredDataDiskUris) { $dataDiskName = "{0}DataDisk{1}" -f $newVmName, $lun; $vmconfig = $vmconfig | Add-AzureRmVMDataDisk -Name $dataDiskName -VhdUri $restoredDataDiskUri -Lun $lun -CreateOption attach; $lun++; } New-AzureRmVM -ResourceGroupName $rgName -Location $location -VM $vmconfig; } Function FindMatchSnapshot($osDiskSnapshotTime, $snapshotPropertiesList) { $count = $snapshotPropertiesList.Count; for ($i = 0; $i -lt $count; $i++) { if ($osDiskSnapshotTime -gt $snapshotPropertiesList[$i].SnapshotTime) { continue; } # wo consider snapshots for different data disks within 2 minutes (120 sec) should be related if (($snapshotPropertiesList[$i].SnapshotTime - $osDiskSnapshotTime).TotalSeconds -lt 120) { return $i; } else { return -1; } } return -1; } Function GetSnapshotListByBlob($blob) { return $blob.Container.ListBlobs($blob.Name, $true, "Snapshots") | Where {$_.SnapshotTime -ne $null}; } Function GetSnapshotPropertiesList($Snapshots, $blob) { $Index = 0 $SnapshotPropertiesList = @() Foreach($Snapshot in $Snapshots) { $Index++ $SnapshotPropertiesList += [PSCustomObject]@{ID = $Index BlobName = $blob.Name SnapshotTime = $Snapshot.SnapshotTime SnapshotUri = $Snapshot.Uri} } return $SnapshotPropertiesList; } #$Cred = New-Object System.Management.Automation.PSCredential("XXXXXXXX@XXXXXXXX.partner.onmschina.cn", (ConvertTo-SecureString "XXXXXXXX" -AsPlainText -Force)); #[void](Login-AzureRmAccount -EnvironmentName AzureChinaCloud -Credential $AzureRMCred); [void](Select-AzureRmSubscription -SubscriptionName $SubscriptionName); RestoreVM $ResourceGroupName $OldVMName $NewVMName;调用方法需要额外指定一个新的虚拟机名字:
PS> &.\[ARM]restore_VM_from_snapshot.ps1 -SubscriptionName <Subscription Name> -ResourceGroupName <Resource Group Name> -OldVMName <Old VM Name> -NewVMName <New VM Name>
脚本执行结果: