Powershell - Move Azure resources between resource groups in bulk

Mon, September 2, 2024 - 2 min read
delete-sub-diagnosticsettings.sh
 
function moveResources {
    [CmdletBinding()]
    param(
        [bool]$onlyValidate,
        [string]$sourceSub,
        [string]$targetSub,
        [array]$rgNames,
        [array]$excludedResourceTypes
    )
    begin {
        $startDate = Get-Date
        #Setup block
        Write-Host "Starting script...$($startDate)"
        # Connect to your Azure account
        Connect-AzAccount
        Set-AzContext -Subscription $targetSub
 
        # Create or update resource groups in the target subscription
        $rgNames | ForEach-Object { New-AzResourceGroup -Name $_ -Location "westeurope" -Force }
 
        # Initialize an empty array for storing resource group information
        $rgs = @()
 
        # Build an array of objects containing resource group details
        foreach ($rg in $rgNames) {
            $rgs += [pscustomobject]@{
                rgName = $rg
                oldId  = "/subscriptions/$sourceSub/resourceGroups/$rg"
                newId  = "/subscriptions/$targetSub/resourceGroups/$rg"
            }
        }
    }
    process {
        Set-AzContext -Subscription $sourceSub
        # Process each resource group in parallel with a throttle limit of 5
        $rgs | Foreach-Object -ThrottleLimit 5 -Parallel {
            # Retrieve resources from the source resource group
            $rgResources = (Get-AzResource -ResourceGroupName $_.rgName) | Where-Object {
        ($_.ParentResource -eq $null -and $_.ResourceType -notin $using:excludedResourceTypes)
            }
 
            # Get unique resource provider namespaces
            $uniqueResourceProvider = $rgResources.Type | Get-Unique
            $formattedUniqueResourceProviders = $uniqueResourceProvider | Split-Path -Parent
 
            # Set the Azure context to the target subscription
            Set-AzContext -Subscription $using:targetSub
 
            # Register resource providers
            $formattedUniqueResourceProviders | ForEach-Object {
                Register-AzResourceProvider -ProviderNamespace $_ -ErrorAction SilentlyContinue
            }
 
            # Create an array of resource IDs
            $resourceIds = @(
                $rgResources.ResourceId
            )
 
            # Output resource details
            $rgResources | Format-Table
 
            # Invoke resource action to validate resource move, stop thread if it throws exception.
            Invoke-AzResourceAction -Action validateMoveResources -ResourceId $_.oldId -Parameters @{
                resources           = $resourceIds
                targetResourceGroup = $_.newId
            } -Force -ErrorAction Stop
 
            # Output a message indicating successful validation
            Write-Host "$($_.rgName) Was validated ok by validateMoveResources!"
 
            if ($using:onlyValidate -ne $true) {
                Write-Host "Moving resources in $($_.rgName)!"
                Read-Host "Press Enter to continue..."
                # Move the resources to the target resource group
                Move-AzResource -DestinationResourceGroupName $_.rgName -DestinationSubscriptionId $using:targetSub -ResourceId $rgResources.ResourceId -Force
            } 
        }        
    }
    end {
        $endDate = Get-Date
        Write-Host "Action complete..."
        Write-Host "Started $($startDate)"
        Write-Host "Ended $($endDate)"
        $delta = New-TimeSpan -Start $startDate -End $endDate
        Write-Host "Total time: $($delta.Minutes)"
    }
}
 
# Define source and target subscription ID
 
$sourceSub = ""
$targetSub = ""
 
# Create an array of resource group names
$rgNames = (
    "rprg01",
    "rprg02",
    "rprg03",
    "rprg04"
)
$excludedResourceTypes = @(
    'microsoft.insights/metricalerts'
)
 
 
moveResources -sourceSub $sourceSub -targetSub $targetSub -rgNames $rgNames -excludedResourceTypes $excludedResourceTypes -onlyValidate $true