?Migrating All Azure Resources Between Subscriptions with Azure CLI & PowerShell?
Migrating resources between Azure subscriptions is a common but risky task. Whether you’re reorganizing tenants, separating billing, or preparing for a handover, doing this manually is slow and error-prone.
The issue here is that Azure Portal does not provide an option to migrate all resources at once.
For this reason, I created a PowerShell + Azure CLI script that automatically migrates all resource groups and their resources from one subscription to another.
? What This Script Does
The script performs the following steps:
- Connects to a source Azure subscription
- Retrieves all resource groups
- Switches to a target subscription
- Creates missing resource groups
- Moves all supported resources to the target subscription
- Logs success and failure clearly
It uses native Azure Resource Manager (ARM) move operations, meaning:
- Resource IDs remain intact
- No redeployment is required
- Downtime is minimized (but not zero)
? Prerequisites
Before running the script, ensure:
- Azure CLI is installed (
az version) - You are logged in (
az login) - You have Owner or Contributor permissions on both subscriptions
- Resources are in a movable state
⚠️ Not all Azure resources support cross-subscription moves (e.g., classic resources, some networking dependencies).
⚙️ Script Configuration
Update the following values before running:
$sourceSubId = "***" # Source subscription ID
$targetSubId = "***" # Target subscription ID
$location = "westeurope" # Location for new resource groups
The $location value is only used when creating missing resource groups in the target subscription.
? Script Walkthrough
1️⃣ Switch to Source Subscription
az account set --subscription $sourceSubId
This ensures all resource discovery happens in the correct source context.
2️⃣ Retrieve All Resource Groups
$resourceGroups = az group list --query "[].name" -o tsv
This fetches every resource group name in the source subscription.
3️⃣ Ensure Resource Groups Exist in Target
az group exists --name $rg
If the resource group doesn’t exist in the target subscription, it is automatically created:
az group create --name $rg --location $location
✔ Prevents failures during resource moves
✔ Keeps naming consistent
4️⃣ Collect Resource IDs
$ids = az resource list --resource-group $rg --query "[].id" -o tsv
Azure requires resource IDs for move operations, not names.
5️⃣ Move Resources Across Subscriptions
az resource move `
--destination-group $rg `
--destination-subscription-id $targetSubId `
--ids $ids
This is the core operation:
- Moves resources
- Keeps them in the same resource group name
- Preserves configuration and metadata
6️⃣ Error Handling & Logging
if ($LASTEXITCODE -eq 0) {
Write-Host "SUCCESS"
} else {
Write-Host "FAILED - see error above"
}
Failures usually occur due to:
- Unsupported resource types
- Dependency constraints
- Resources spanning multiple resource groups
⚠️ Important Limitations
Be aware of these Azure constraints:
❌ Some resources cannot be moved
- Classic resources
- Certain App Service plans
- Managed identities with dependencies
❌ Resources must move together
- VNets + subnets
- NICs + VMs
- Disks + VMs
✔ Azure will block the move if dependencies are violated
✅ Best Practices Before Running
✔ Test on a single resource group first
✔ Export ARM templates as a backup
✔ Run during a maintenance window
✔ Validate networking dependencies
✔ Monitor activity logs during execution
? When Should You Use This Script?
This approach is ideal for:
- Subscription consolidation
- Tenant separation
- Environment restructuring (Dev → Prod)
- M&A cloud migrations
- Billing realignment
? Final Thoughts
This script provides a clean, repeatable, and safe way to migrate Azure resources at scale using native tooling from Microsoft Azure.
It’s not magic—but with proper preparation, it can save hours or days of manual work.
If you found this useful, feel free to:
? Like
? Repost
? Share your migration war stories
Happy migrating ☁️?
----- SCRIPT ------
Write-Host "======================================"
$sourceSubId = "***"
$targetSubId = "***"
$location = "westeurope"
Write-Host $sourceSubId
Write-Host $targetSubId
# Switch to source subscription
Write-Host Setting + $targetSubId
az account set --subscription $sourceSubId
# Get list of all resource group names
$resourceGroups = az group list --query "[].name" -o tsv
foreach ($rg in $resourceGroups) {
Write-Host "======================================" -ForegroundColor Cyan
Write-Host "Processing source RG: $rg" -ForegroundColor Yellow
# Check if same-name RG already exists in TARGET subscription
az account set --subscription $targetSubId
$exists = az group exists --name $rg --output tsv
if ($exists -eq "false") {
Write-Host " Creating target RG '$rg' in location $location ..." -ForegroundColor Green
az group create --name $rg --location $location --subscription $targetSubId
} else {
Write-Host " Target RG '$rg' already exists - skipping creation" -ForegroundColor Green
}
# Switch back to source to list resources
az account set --subscription $sourceSubId
# Get resource IDs from source RG
$ids = az resource list --resource-group $rg --query "[].id" -o tsv
if ($ids) {
Write-Host " Moving resources from '$rg' ..." -ForegroundColor Yellow
az resource move `
--destination-group $rg `
--destination-subscription-id $targetSubId `
--ids $ids
if ($LASTEXITCODE -eq 0) {
Write-Host " SUCCESS" -ForegroundColor Green
} else {
Write-Host " FAILED - see error above. Often due to dependencies or unsupported resource types." -ForegroundColor Red
}
} else {
Write-Host " No resources found in '$rg' - skipping move" -ForegroundColor Gray
}
}
Write-Host "======================================" -ForegroundColor Cyan
Write-Host "All resource groups processed." -ForegroundColor White