CopyVMCrossTenant is a script to copy a VM from one Tenant to an other.
Azure subscriptions are part of Azure AD tenants, they can be moved from one tenant to the other, but it is not always possible
- different ownerships
- some resources cannnot move across
- the source subscription might need to remain in-place
- ...
this script will perform the actual copy of the VM including its undering resources providing the VM is not too complex.
- Copy VM across tenant with same name, vnet name and convert disks to managed disks
- Copy VM inside tenant to another subscription with same name , vnet name
- Delete/Recreate VM in a different vnet in the same subscription
-
create dependant Virtual networks with the same address space and subnets
-
create Windows or Linux VM enveloppe at the target preserving the following items
- network interfaces (static ips are not preserved)
- VM name
- Tags
-
assign all network interfaces to a single network security group
-
create the target storage account if it does not exist yet in the target resource group
-
copy blob-based or managed disk based OS and Data drives to the target and transform them into Managed Disks using a simple naming such as vmname-OS, vmnamem-data-1 ... for BHD files
.\CopyVMCrossTenant.ps1 -resourcegroupname mytargetrg -vmname myVM
** Public IPs are NOT preserved if needed they could be added later using an auxilairy script **
.\CopyVMCrossTenant.ps1 -resourcegroupname myrg -vmname myVM -targetvnet vnetxxx -samesubscription
the script create vnets (per resource group) at the target but does not create peering for now. Peering must be setup :
# $sourcecontext must contain the azcontext for the source tenant
# replace hubnetname with the hubvnet
#
## setup hub and spoke
$hubvnetname ="hubvnetname"
$hub = get-azvirtualnetwork -name $hubvnetname -azcontext $sourcecontext
$spokes = get-azvirtualnetwork -name "hubvnetname" -azcontext $sourcecontext |%{ $_.Name -ne $hubvnetname }
foreach ($spoke in $spokes) {
Add-AzVirtualNetworkPeering -Name "$($hub.name)-$($spoke.name)" -VirtualNetwork $hub -RemoteVirtualNetworkId $spoke.Id -AllowGatewayTransit
Add-AzVirtualNetworkPeering -Name "$($spoke.name)-$($hub.name)" -VirtualNetwork $spoke -RemoteVirtualNetworkId $hub.Id -UseRemoteGateways -AllowForwardedTraffic
}
you must have the required permission on both the source and the target subscriptions, ideally you would use an Azure AD app and a certificate to get access to it , see (https://learn.microsoft.com/en-us/powershell/azure/authenticate-azureps?view=azps-10.1.0)
to be able to use both tenant's credentials, we must store the cred as "Azure Context" and give them a name.
the current context for the script will be the target context, the source context must exist and its name be passed to the script.
Connect-AzAccount -tenantid $sourcetenant -subscriptionid
$sourcesub -contextname "Source" -Force
Connect-AzAccount -tenantid $targettenant -subscriptionid $targetsub -contextname "Target" -Force
select-azcontext Target
if you are using the preferred method of using an AppID and Certificate,
- create an AppID in one tenant, make it Cross tenant
- grant the subscription owner permission to that app
- Create a certificate for the App
- add the Service Principal for this AppID in the other tenant
- grant the service principal in the local tenant with the required permission to create VMs, virtual networks ...
- connect with certificate using the following
Connect-AzAccount -CertificateThumbprint $thumbprint -ServicePrincipal -tenantid $sourcetenant -subscriptionid
$sourcesub -contextname "Source" -Force
Connect-AzAccount -CertificateThumbprint $thumbprint -ServicePrincipal -tenantid $targettenant -subscriptionid $targetsub -contextname "Target" -Force
select-azcontext Target
- copy many vm across tenant
## copy all VMS from one RG
## to create context use connect-azaccount -contextname
$sourcecontext = get-azcontext -Name "SourceContextName"
get-azvm -azcontext $sourcecontext -resourcegroupname "MyRG" |%{
.\CopyVMCrossTenant.ps1 -resourcegroupname $_.resourcegroupname -vmname $_.name -sourcecontext $sourcecontext
}
optionnally you can add the -removesource
To diagnoze, enable boot diagnostics and review console screen shot
Usually with VMs that have several disks , try removing data disk and rebooti
if that works, check disk signature with diskpart
diskpart list disk (usually 3 disks here with the temp D disk)
sel dis 0
uniqueid disk
sel dis 2
uniqueid disk
if both are the same , use
uniqueid disk ID=4324234 or to fix disk signature conflict.
usually, when the HyperV Generation is wrong, you may have created a V1 VM for a V2 source
- delete the VM (remove-azvm)
- delete the disk (remove-azdisk)
- run the script again with -Generation2 switch to force Gen2
the HyperV Generation might be wrong
A feature of the Intune agent is that it will block inbound traffic if the connectivity with intune is broken. Depending on the network setup you may need to disable intune to be able to connect back to the VM using RDP.