Last active
March 3, 2021 23:53
-
-
Save marckean/7b7760e40d298b71ef925b2ccff99166 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
Written with version 2.8.0 of the Azure PowerShell Module | |
available from here https://github.com/Azure/azure-powershell/releases/tag/v2.8.0-October2019 | |
or run: Install-Module -Name Az -RequiredVersion 2.6.0 -AllowClobber | |
Migration instructions Azure.RM to Az - https://azure.microsoft.com/en-au/blog/how-to-migrate-from-azurerm-to-az-in-azure-powershell/ | |
#> | |
# from https://poshtools.com/2018/04/10/cross-platform-out-gridview-using-universal-dashboard/ | |
function Out-GridView | |
{ | |
<# | |
.SYNOPSIS | |
Send objects to Out-Gridview in Windows PowerShell | |
.DESCRIPTION | |
This command is intended as a workaround for PowerShell Core running on a Windows platform, presumably Windows 10. PowerShell Core does not support all of the .NET Framework which means some commands like Out-Gridview are not supported. However, on a Windows desktop you are most likely running Windows PowerShell side by side with PowerShell Core. This command is designed to take objects from a PowerShell expression and send it to an instance of Windows PowerShell running Out-Gridview. You can specify a title and pass objects back to your PowerShell Core session. Note that passed objects will be deserialized versions of the original objects. | |
.PARAMETER Title | |
Specify a Title for the Out-Gridview window | |
.PARAMETER Passthru | |
Pass selected objects from Out-Gridview back to your PowerShell Core session. | |
.EXAMPLE | |
PS C:\> get-childitem c:\scripts\*.ps1 | Send-ToPSGridview -title "My Scripts" | |
Press Enter to continue...: | |
Display all ps1 files in Out-Gridview. You must press Enter to continue. | |
.EXAMPLE | |
PS C:\> get-service | where status -eq 'running' | Send-ToPSGridView -Passthru | Restart-service -PassThru | |
Press Enter to continue...: | |
Status Name DisplayName | |
------ ---- ----------- | |
Running BluetoothUserSe... Bluetooth User Support Service_17b710 | |
Running Audiosrv Windows Audio | |
Get all running services and pipe to Out-Gridview where you can select services which will be restarted. | |
.EXAMPLE | |
PS C:\> $val = 1..10 | Send-ToPSGridView -Title "Select some numbers" -Passthru | |
Press Enter to continue...: | |
PS C:\> $val | |
4 | |
8 | |
6 | |
PS C:\> $val | Measure-Object -sum | |
Count : 3 | |
Average : | |
Sum : 18 | |
Maximum : | |
Minimum : | |
StandardDeviation : | |
Property : | |
Send the numbers 1 to 10 to Out-Gridview where you can select a few. The results are saved to a variable in the PowerShell Core session where you can use them. | |
.INPUTS | |
System.Object | |
.OUTPUTS | |
None, Deserialized.System.Object[] | |
.NOTES | |
Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/ | |
#> | |
[cmdletbinding()] | |
[alias("ogv")] | |
[OutputType("None", "Deserialized.System.Object[]")] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline, Mandatory)] | |
[ValidateNotNullOrEmpty()] | |
[object[]]$InputObject, | |
[ValidateNotNullOrEmpty()] | |
[string]$Title = "Out-GridView", | |
[switch]$Passthru | |
) | |
Begin { | |
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)" | |
#validate running on a Windows platform` | |
if ($PSVersionTable.Platform -ne 'Win32NT') { | |
Throw "This command requires a Windows platform with a graphical operating system like Windows 10." | |
} | |
#initialize an array to hold pipelined objects | |
$data = @() | |
#save foreground color | |
$fg = $host.ui.RawUI.ForegroundColor | |
} #begin | |
Process { | |
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Adding object" | |
$data += $InputObject | |
} #process | |
End { | |
Write-Verbose "[$((Get-Date).TimeofDay) END ] Creating a temporary xml file with the data" | |
$tempFile = Join-Path -path $env:temp -ChildPath ogvTemp.xml | |
$data | Export-Clixml -Path $tempFile | |
Write-Verbose "[$((Get-Date).TimeofDay) END ] Starting up a PowerShell.exe instance" | |
PowerShell.exe -nologo -noprofile -command { | |
param([string]$Path, [string]$Title = "Out-Gridview", [bool]$Passthru) | |
Import-Clixml -Path $path | Out-Gridview -title $Title -passthru:$Passthru | |
#a pause is required because if you don't use -Passthru the command will automatically complete and close | |
$host.ui.RawUI.ForegroundColor = "yellow" | |
Pause | |
} -args $tempFile, $Title, ($passthru -as [bool]) | |
if (Test-Path -Path $tempFile) { | |
Write-Verbose "[$((Get-Date).TimeofDay) END ] Removing $tempFile" | |
Remove-Item -Path $tempFile | |
} | |
#restore foreground color | |
$host.ui.RawUI.ForegroundColor = $fg | |
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)" | |
} #end | |
} #close Send-ToPSGridView | |
########################################################################################## | |
######################## Migration Motivation & Direction ######################### | |
########################################################################################## | |
#region Migration Motivation | @marckean | |
#creating object os WScript | |
$wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop | |
$SourceTargetAccount = $null # Yes = 6, No = 7 | |
$SourceTargetAccount = $wshell.Popup("Do you use the same logon account to access both Source & Target subscriptions?",0,"Hello User?",48+4) | |
########################################################################################## | |
############################ Logon to Source & Target ############################# | |
########################################################################################## | |
#region Logon to Source & Target environment | @marckean | |
$TargetUserProfile = $null | |
if ($SourceTargetAccount -eq '7') { # Yes = 6, No = 7 | |
# Logon to Source environment | @marckean | |
cls | |
Write-Host "`nEnter credentials for the SOURCE Azure subscription.`n" -ForegroundColor Cyan | |
$SourcePath = "$($env:TEMP)\$(Get-Date -Format yyyyMMdd)SourceEnv.json" | |
Remove-Item -Path $SourcePath -Force -ErrorAction SilentlyContinue | |
#Clear-AzContext -Scope CurrentUser;Clear-AzContext -Scope Process | |
Connect-AzAccount | |
$SourceSubscriptionList = Get-AzSubscription | |
$SourceSubscription = ($SourceSubscriptionList | Out-GridView -Title "Choose a Source Subscription ..." -PassThru) | |
Select-AzSubscription -Subscription $SourceSubscription | |
Save-AzContext -Path $SourcePath -Force | |
# Logon to Target environment | @marckean | |
Write-Host "`nEnter credentials for the TARGET Azure subscription.`n" -ForegroundColor Cyan | |
$TargetPath = "$($env:TEMP)\$(Get-Date -Format yyyyMMdd)TargetEnv.json" | |
Remove-Item -Path $TargetPath -Force -ErrorAction SilentlyContinue | |
#Clear-AzContext -Scope CurrentUser;Clear-AzContext -Scope Process | |
Connect-AzAccount | |
$TargetSubscriptionList = Get-AzSubscription | |
$TargetSubscription = ($TargetSubscriptionList | Out-GridView -Title "Choose a Target Subscription ..." -PassThru) | |
Select-AzSubscription -Subscription $TargetSubscription | |
Save-AzContext -Path $TargetPath -Force | |
} | |
else { | |
# Logon to Azure environment | @marckean | |
Write-Host "`nEnter credentials for the SOURCE Azure subscription.`n" -ForegroundColor Cyan | |
$SourcePath = "$($env:TEMP)\$(Get-Date -Format yyyyMMdd)SourceEnv.json" | |
$TargetPath = "$($env:TEMP)\$(Get-Date -Format yyyyMMdd)TargetEnv.json" | |
Remove-Item -Path $SourcePath -Force -ErrorAction SilentlyContinue | |
Remove-Item -Path $TargetPath -Force -ErrorAction SilentlyContinue | |
#Clear-AzContext -Scope CurrentUser;Clear-AzContext -Scope Process | |
Connect-AzAccount | |
$SubscriptionList = Get-AzSubscription | |
#Source Subscription | |
$SourceSubscription = ($SubscriptionList | Out-GridView -Title "Choose a Source Subscription ..." -PassThru) | |
Select-AzSubscription -Subscription $SourceSubscription | |
Save-AzContext -Path $SourcePath -Force | |
#Target Subscription | |
$TargetSubscription = ($SubscriptionList | Out-GridView -Title "Choose a Target Subscription ..." -PassThru) | |
Select-AzSubscription -Subscription $TargetSubscription | |
Save-AzContext -Path $TargetPath -Force | |
} | |
#endregion | |
########################################################################################## | |
################################### Functions #################################### | |
########################################################################################## | |
#region Azure Subscription Functions | @marckean | |
Function SourceSubscription {Import-AzContext -Path $SourcePath} | |
Function TargetSubscription {Import-AzContext -Path $TargetPath} | |
function MDtoSAStoMD { | |
TargetSubscription | |
# Check to see if the target disk already exists | |
if (!(Get-AzDisk -ResourceGroupName $targetResourceGroupName -DiskName $ManagedDisk.TargetName -ea SilentlyContinue)) { | |
SourceSubscription | |
# Get a copy of the current managed disk while VM is in use to use as a temp disk | |
if ($ShutDownDirection -eq '7' -or !$ShutDownDirection) { | |
# Create a TEMP managed disk of the running VM in the same region | |
$TempSourceDiskName = ('TEMP_' + $ManagedDisk.SourceName) | |
# Create temp copy of the original disk | |
$diskConfig = New-AzDiskConfig -SourceResourceId $managedDisk.Id -Location $managedDisk.Location -CreateOption Copy | |
New-AzDisk -Disk $diskConfig -DiskName $TempSourceDiskName -ResourceGroupName $managedDisk.ResourceGroupName | |
$sas = Grant-AzDiskAccess -ResourceGroupName $ManagedDisk.ResourceGroupName -DiskName $TempSourceDiskName ` | |
-DurationInSecond 3600 -Access Read | |
} | |
if ($ShutDownDirection -eq '6') { # Yes = 6, No = 7 | |
$sas = Grant-AzDiskAccess -ResourceGroupName $ManagedDisk.ResourceGroupName -DiskName $ManagedDisk.SourceName ` | |
-DurationInSecond 3600 -Access Read | |
} | |
TargetSubscription | |
$StorageAccount = New-AzStorageAccount -Name (Get-Random -Minimum 11111111111111 -Maximum 99999999999999) ` | |
-ResourceGroupName $TargetResourceGroupName -SkuName Standard_LRS -Location $TargetLocation | |
$StorageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $StorageAccount.ResourceGroupName ` | |
-Name $StorageAccount.StorageAccountName | |
$destContext = New-AzStorageContext –StorageAccountName $StorageAccount.StorageAccountName ` | |
-StorageAccountKey ($StorageAccountKey).Value[0] | |
#Ensure the container is created to store the OS VHD | @marckean | |
$containerName = 'vhds' | |
if (!(Get-AzStorageContainer -Context $destContext | where {$_.Name -eq $containerName})) { | |
cls | |
Write-Verbose "Container $containerName not found. Creating..." | |
New-AzStorageContainer -Context $destContext -Name $containerName -Permission Off | |
} | |
$blob = Start-AzStorageBlobCopy -AbsoluteUri $sas.AccessSAS -DestContainer 'vhds' ` | |
-DestContext $destContext -DestBlob ($ManagedDisk.TargetName + ".vhd") | |
# Retrieve the current status of the copy operation | @marckean | |
$status = $blob | Get-AzStorageBlobCopyState | |
Start-Sleep 5; $status; Start-Sleep 5; $status | |
Write-Host "`n`tCopying the Managed Disk" $ManagedDisk.SourceName "..." -ForegroundColor Cyan | |
#Loop until complete | @marckean | |
While ($status.Status -eq "Pending") { | |
$status = $blob | Get-AzStorageBlobCopyState | |
Start-Sleep 10 | |
#Print out status | |
Write-Host "`n`tCopying the Managed Disk..." -ForegroundColor Cyan | |
$status | Out-Host | |
} | |
# Create the managed disk - convert storage based VHD to a managed disk | |
$VHDContainer = Get-AzStorageContainer -Context $destContext -ErrorAction SilentlyContinue | ? {$_.Name -like $containerName} | |
$BlobEndPoint = (Get-AzStorageBlob -Container $containerName -Context $destContext | | |
? {$_.Name -match ($ManagedDisk.TargetName)}).Context.BlobEndPoint | |
$AzsrcUri = "{0}{1}/{2}" -f $BlobEndPoint, $VHDContainer.Name, (($ManagedDisk.TargetName) + ".vhd") | |
$diskConfig = New-AzDiskConfig -AccountType $ManagedDisk.AccountType -Location $TargetLocation ` | |
-CreateOption Import -SourceUri $AzsrcUri -StorageAccountId $StorageAccount.Id ` | |
-DiskSizeGB $ManagedDisk.DiskSizeGB -OsType $ManagedDisk.OsType | |
# Create a new managed disk in the target subscription from the copied disk | |
New-AzDisk -Disk $diskConfig -DiskName $ManagedDisk.TargetName -ResourceGroupName $targetResourceGroupName | |
# Clean up at the end | |
SourceSubscription | |
if ($ShutDownDirection -eq '7' -or !$ShutDownDirection) { # Yes = 6, No = 7 | |
Revoke-AzDiskAccess -ResourceGroupName $ManagedDisk.ResourceGroupName -DiskName $TempSourceDiskName | |
# Remove the TEMP managed disk | |
Remove-AzDisk -ResourceGroupName $ManagedDisk.ResourceGroupName -DiskName $TempSourceDiskName -Force | |
} | |
if ($ShutDownDirection -eq '6') { # Yes = 6, No = 7 | |
# Working with production source disks, don't remove them | |
Revoke-AzDiskAccess -ResourceGroupName $ManagedDisk.ResourceGroupName -DiskName $ManagedDisk.SourceName | |
} | |
TargetSubscription | |
Remove-AzStorageAccount -ResourceGroupName $TargetResourceGroupName -Name $StorageAccount.StorageAccountName -Force | |
} | |
} | |
#endregion | |
########################################################################################## | |
####################### Select Source Virtual Machine ############################ | |
########################################################################################## | |
#region Select Azure Source Virtual Machine | @marckean | |
cls | |
SourceSubscription | |
write-host -nonewline "`n`tIn subscription... " -ForegroundColor Yellow; ` | |
write-host -nonewline $SourceSubscription.Name`n -ForegroundColor Green; | |
write-host -nonewline "`n`tPlease choose a " -ForegroundColor Yellow; ` | |
write-host -nonewline "Virtual Machine " -ForegroundColor Green | |
start-sleep -seconds 1 | |
# Get Virtual Machines that contain Managed Disks only - either as an OS Disk or as a Data Disk | |
<#$VirtualMachines = Find-AzResource -ExpandProperties -ResourceType 'Microsoft.Compute/virtualMachines' | | |
? {$_.Properties.storageProfile.osDisk.managedDisk -or $_.Properties.storageProfile.dataDisks.managedDisk}#> | |
$VirtualMachines = Get-AzVM | ? {$_.storageProfile.osDisk.managedDisk -or $_.storageProfile.dataDisks.managedDisk} | select * | |
$SourceVM = ($VirtualMachines | Out-GridView -Title "Select a source Virtual Machine ..." -PassThru) | |
#$SourceVM = Get-AzResource -ResourceGroupName $SourceVM.ResourceGroupName -ResourceType $SourceVM.Type -ApiVersion 2019-08-01 | select * -ExpandProperties | |
write-host -nonewline "`n`tYou selected this Virtual Machine: " -ForegroundColor Yellow; ` | |
write-host -nonewline $SourceVM.name`n -ForegroundColor Green; ` | |
start-sleep -seconds 3 | |
#endregion | |
########################################################################################## | |
######################## Virtual Machine Managed Disks ############################# | |
########################################################################################## | |
#region Virtual Machine Data Disks | @marckean | |
SourceSubscription | |
# Get any data disks associated with the source VM | |
$managedOSDiskname = $SourceVM.storageProfile.osDisk.name | |
#endregion | |
########################################################################################## | |
############################## Target Location ################################### | |
########################################################################################## | |
#region Virtual Machine Data Disks | @marckean | |
SourceSubscription | |
$locations = Get-AzLocation | |
$TargetLocation = ($locations | Out-GridView -Title "Choose a target location where to copy the managed disks to ..." -PassThru).location | |
#endregion | |
########################################################################################## | |
############################ Target Resource Group ################################ | |
########################################################################################## | |
#region Target Azure Resource Group - Region and Resource Group creation | @marckean | |
######################## | Select Azure RM Resource Group | ####################### | @marckean | @marckean | |
TargetSubscription | |
cls | |
Write-Host "`n`tGathering a list of all Azure ARM Resource Groups Azure..." -ForegroundColor Cyan | |
$TargetResourcegroups = Get-AzResourceGroup | select ResourceGroupName, Location -ErrorAction SilentlyContinue | |
$TargetResourcegroups | ft | |
write-host -nonewline "Existing Resource Groups listed above (copy & paste) an existing one or enter a new Resource Group name: " ` | |
-ForegroundColor Green | |
$choice = read-host | |
$TargetResourceGroupName = $choice -replace "\s", "" | |
write-host -nonewline "`n`tThe Resource Group where new managed disk will be moved to: " -ForegroundColor Yellow; ` | |
write-host -nonewline $TargetResourceGroupName`n -ForegroundColor Green; ` | |
start-sleep -seconds 3 | |
####################### | Create the target Resource Group - if it doesn't exist | ####################### | @marckean | |
cls | |
Write-Host "`n`tCreating the target Resource group $TargetResourceGroupName (if it don't exist already)..." ` | |
-ForegroundColor Cyan | |
if (!(Get-AzResourceGroup -Name $TargetResourceGroupName -ErrorAction SilentlyContinue)) { | |
New-AzResourceGroup -Name $TargetResourceGroupName -Location $TargetLocation -Force | |
} | |
#endregion | |
########################################################################################## | |
################# Build the Managed Disk Object for use later ####################### | |
########################################################################################## | |
#region Build the Managed Disk Object for use later | |
SourceSubscription | |
#Get the source managed disks | |
$ManagedDisks = @() | |
$managedOSDisk = Get-AzDisk | ? {$_.Name -eq $managedOSDiskName} | |
# Data disks | |
foreach ($ManagedDisk in $SourceVM.storageProfile.dataDisks) { | |
$DataDisk = (Get-AzDisk | ? {$_.Name -eq $ManagedDisk.Name}) | |
$x = [PSCustomObject] @{ | |
SourceName = $ManagedDisk.Name | |
TargetName = '{0}{5}{4}{5}{1}{2}{5}{3}' -f 'Data', 'Lun', $ManagedDisk.lun, (Get-Date -Format yyyyMMdd), $SourceVM.Name, '_' | |
ResourceGroupName = $DataDisk.ResourceGroupName | |
Lun = $ManagedDisk.lun | |
Location = $DataDisk.location | |
Id = $DataDisk.id | |
AccountType = $DataDisk.Sku.Name | |
DiskSizeGB = $DataDisk.DiskSizeGB | |
DiskType = 'Data' | |
} | |
$ManagedDisks += $x | |
} | |
# OS disks | |
$x = [PSCustomObject] @{ | |
SourceName = $managedOSDisk.Name | |
TargetName = '{0}{3}{2}{3}{1}' -f 'OS', (Get-Date -Format yyyyMMdd), $SourceVM.Name, '_' | |
ResourceGroupName = $managedOSDisk.ResourceGroupName | |
Lun = $managedOSDisk.lun | |
Location = ($managedOSDisk).location | |
Id = ($managedOSDisk).id | |
AccountType = ($managedOSDisk).Sku.Name | |
DiskSizeGB = $managedOSDisk.diskSizeGB | |
DiskType = 'OS' | |
OsType = ($managedOSDisk).OsType | |
} | |
$ManagedDisks += $x | |
#endregion | |
########################################################################################## | |
############################# Shutdown Source VM ################################# | |
########################################################################################## | |
#region Shut-down source VM | @marckean | |
if ($ManagedDisks.disktype -contains 'Data') { | |
SourceSubscription | |
$ProvisioningState = (Get-AzVM -ResourceGroupName $SourceVM.ResourceGroupName -Name $SourceVM.Name -Status).Statuses.displaystatus | |
if ($ProvisioningState -notcontains 'VM deallocated') { | |
# Yes = 6, No = 7 | |
$ShutDownDirection = $wshell.Popup("As you have multiple disks, it's strongly suggested we shut-down the source VM?", 0, "Shutdown Direction", 48 + 4) | |
if ($ShutDownDirection -eq '6') { | |
cls | |
write-host -nonewline "`n`tShutting down... " -ForegroundColor Yellow; ` | |
write-host -nonewline $SourceVM.Name`n -ForegroundColor Green | |
start-sleep -seconds 1 | |
Stop-AzVM -Name $SourceVM.Name -ResourceGroupName $SourceVM.ResourceGroupName -Force | |
} | |
} | |
} | |
#endregion | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
################### The script does the work, grab a coffee ######################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
########################################################################################## | |
cls | |
Write-Host "`n`tThe script is doing the work, grab a coffee..." -ForegroundColor Cyan; | |
write-host -nonewline "`n`tCheck the target Resource Group shortly for the new copied Managed Disk...`n`n" ` | |
-ForegroundColor Yellow | |
########################################################################################## | |
################### Copy Managed Disk within the same Tenant ######################## | |
########################################################################################## | |
#region If the Source and Target user account is the same | |
if ($SourceTargetAccount -eq '6') { # Yes = 6, No = 7 | |
TargetSubscription | |
foreach ($ManagedDisk in $ManagedDisks) { | |
# In the same location | |
if ($ManagedDisk.Location -eq $TargetLocation) { | |
$diskConfig = New-AzDiskConfig -SourceResourceId $managedDisk.Id -Location $TargetLocation -CreateOption Copy | |
#Create a new managed disk in the target subscription and resource group | |
$TargetManagedDisk = New-AzDisk -Disk $diskConfig -DiskName $ManagedDisk.TargetName ` | |
-ResourceGroupName $targetResourceGroupName | |
} | |
# In different locations | |
if ($ManagedDisk.Location -ne $TargetLocation) { | |
MDtoSAStoMD | |
} | |
} | |
} | |
#endregion | |
########################################################################################## | |
####################### Copy Managed Disk Across Tenants ############################ | |
########################################################################################## | |
#region If the Source and Target user account is NOT the same | |
if ($SourceTargetAccount -eq '7') { # Yes = 6, No = 7 | |
foreach ($ManagedDisk in $ManagedDisks) { | |
MDtoSAStoMD | |
} | |
} | |
#endregion |
FYI, this script works only in PowerShell Core now. There is no $PSVersionTable.Platform property in Windows PowerShell.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Line 126 $destContext = New-AzureStorageContext –StorageAccountName $StorageAccount.StorageAccountName `
has an invalid character – instead of an -. If you look extremely carefully above, all the other "-" display in red, whereas this line the " – " displays black. I could not run the script until I tracked this down and replace the – with an -. Hope that saves someone a couple of hours debugging!