Skip to content

Instantly share code, notes, and snippets.

@marckean
Last active March 3, 2021 23:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save marckean/7b7760e40d298b71ef925b2ccff99166 to your computer and use it in GitHub Desktop.
Save marckean/7b7760e40d298b71ef925b2ccff99166 to your computer and use it in GitHub Desktop.
<#
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
@BVMather
Copy link

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!

@SvenAelterman
Copy link

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