Version: 0.9.69
March 2026
RGCOPY (Resource Group COPY) is a tool that copies the most important resources of an Azure resource group (source RG) to a new resource group (target RG). It can copy a whole landscape consisting of many servers within a single Azure resource group to a new resource group. The target RG might be in a different region or subscription. RGCOPY has been tested on Windows and Linux
The following example demonstrates the user interface of RGCOPY
$rgcopyParameter = @{
sourceRG = 'sap_vmss_zone'
targetRG = 'sap_vmss_zone_copy'
targetLocation = 'eastus'
setVmSize = 'Standard_E4ds_v4'
setDiskSku = 'Premium_LRS'
}
.\rgcopy.ps1 @rgcopyParameterRGCOPY has been developed for copying an SAP landscape and testing Azure with SAP workload. Therefore, it supports the most important Azure resources needed for SAP, for example VMs, disks, load balancers, storage accounts including content of containers and shares.
📝 Note: The list of supported Azure resources is maintained in the RGCOPY documentation: https://github.com/Azure/RGCOPY/blob/main/rgcopy-docu.md#Supported-Azure-Resources
RGCOPY has different operation modes. By default, RGCOPY is running in Copy Mode.
- In Copy Mode, an BICEP or ARM template is exported from the source RG, modified and deployed in the target RG. Disks are copied using snapshots. You can change several resource properties in the target RG:
- Changing VM size, disk performance tier, disk bursting, disk caching, Write Accelerator, Accelerated Networking
- Adding, removing, and changing availability configuration: Proximity Placement Groups, Availability Sets, Availability Zones, and VM Scale Sets
- Converting disk SKUs
Premium_LRS,StandardSSD_LRS,Standard_LRS,Premium_ZRS,StandardSSD_ZRS,UltraSSD_LRSandPremiumV2_LRSusing (incremental) snapshots and snapshot copy. Changing the logical sector size is not possible. - Converting disks to NetApp Volumes and vice versa using file copy
- In Clone Mode, a VM is cloned within the same resource group. This can be used for adding application servers
- In Merge Mode, a VM is merged into a different resource group. This can be used for copying a jump box to a different resource group.
- In Update Mode, you can change resource properties in the source RG, for example VM size, disk performance tier, disk bursting, disk caching, Write Accelerator, Accelerated Networking. For saving costs of unused resource groups, RGCOPY can do the following:
- Changing disk SKU to 'Standard_LRS' (if the source disk has a logical sector size of 512 byte)
- Deletion of an Azure Bastion including subnet and IP Address (or creation of a Bastion)
- Deletion of all snapshots in the source RG
- Stopping all VMs in the source RG
- Changing NetApp service level to 'Standard' (or any other service level)
- Install the newest version of PowerShell 7
- Install Azure PowerShell Module Az in the newest version:
Install-Module -Name Az -Scope AllUsers -AllowClobber -Force - Download the RGCOPY repository from GitHub (
Download ZIPfrom the popup-menu<> Code). Unblock the downloaded zip file using the PowerShell commandUnblock-File -path <zip file name>and extract the zip file to the user home directory (~). - RCCOPY automatically installs BICEP and AZCOPY on Windows and Linux if it's not already installed. If you want to upgrade an existing version of BICEP or AZCOPY then start RGCOPY with parameters
updateBicepandupdateAzcopyonce. - In PowerShell 7, run
Connect-AzAccount -AuthScope Storage -Subscription '<SubscriptionName>'for each subscription and each Azure Account that will be used by RGCOPY. - The used Azure accounts should have the RBAC role
Contributoron the source RG and on the target subscription (on the target RG, if it already exists). Additional RBAC roles for storage accounts are required for multi-tenant scenarios or when file copy or storage account copy is used (see below). This is even the case when RBAC roleOwneris assigned.
The following examples show the usage of RGCOPY. In all examples, a source RG with the name 'SAP_master' is copied to the target RG 'SAP_copy'. For better readability, the examples use parameter splatting, see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting. Before starting RGCOPY, you must run the PowerShell cmdlet Connect-AzAccount.
# connect to Azure
Update-AzConfig -EnableLoginByWam $true
Connect-AzAccount `
-AuthScope 'Storage' `
-TenantId '7b5ebd57-e5fd-445f-a920-55897cd71921' `
-Subscription 'Contoso Subscription'
# start RGCOPY using cached credentials
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
}
.\rgcopy.ps1 @rgcopyParameterYou might have cached credentials for different subscriptions and users. In this case, you must specify user and subscription using RGCOPY parameters:
$rgcopyParameter = @{
# parameters for subscription and user
sourceSub = 'Contoso Subscription'
sourceSubUser = 'user@contoso.com'
sourceSubTenant = '7b5ebd57-e5fd-445f-a920-55897cd71921'
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
}
.\rgcopy.ps1 @rgcopyParameterYou can store often used parameters in a separate parameter file and pass the filename to RGCOPY. The example above looks like this when having the parameter file parameterFiles\contoso.json
$rgcopyParameter = @{
# using a parameter file
parameterFile = 'parameterFiles\contoso.json'
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
}
.\rgcopy.ps1 @rgcopyParameter{
// file 'parameterFiles\contoso.json'
"sourceSub": "Contoso Subscription",
"sourceSubUser": "user@contoso.com",
"sourceSubTenant": "7b5ebd57-e5fd-445f-a920-55897cd71921",
"targetSub": "Contoso Subscription",
"targetSubUser": "user@contoso.com",
"targetSubTenant": "7b5ebd57-e5fd-445f-a920-55897cd71921"
}You can change almost all properties of VMs and disks in the target RG. The following example changes the VM size to Standard_M16ms (for VMs HANA1 and HANA2), Standard_M8ms (for VM SAPAPP) and Standard_D2s_v4 (for all other VMs):
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
setVmSize = @(
'Standard_M16ms @ HANA1, HANA2',
'Standard_M8ms @ SAPAPP',
'Standard_D2s_v4'
)
}
.\rgcopy.ps1 @rgcopyParameterIn Copy Mode, the workflow of RGCOPY consists of the following steps. RGCOPY decides on its own for each step whether it is needed. However, you can skip each step separately using an RGCOPY switch parameter.
| Step | parameter skip switch |
usage |
|---|---|---|
| 🕛 create BICEP or ARM template | skipArmTemplate |
This step creates the BICEP (or ARM) template that will used for deploying in the target RG. 📝 Note: The template refers either to the snapshots in the source RG or target RG. Therefore, the template is only valid as long as these snapshots exist. |
| 🕐 create snapshots | skipSnapshots |
This step creates snapshots of disks (and NetApp Volumes) in the source RG. During this time, VMs with more than one data disk must be stopped. See section Application Consistency for details. 💡 Tip: When setting parameter switch stopVMsSourceRG, RGCOPY stops all VMs in the source RG before creating snapshots. |
| 🕑 create backups | skipBackups |
This step is only needed when using (or converting) NetApp Volumes on LINUX. A file backup of specified mount points is created on an Azure NFS file share in the source RG. |
| 🕒 copy snapshots | skipRemoteCopy |
This step is needed when the source RG and the target RG are not in the same region. |
| 🕓 deployment | The deployment consists of several part steps:
|
|
| 🕔 start workload | optional step | This step is used for testing SAP Workload. It has to be explicitly activated using switch startWorkload. |
| 🕕 cleanup | optional step | By default, created snapshots in the source RG are not deleted by RGCOPY. 💡 Tip: you can activate a cleanup using RGCOPY parameters. See section Cost Efficiency for details. |
💡 Tip: When setting the parameter switch
simulate, only an ARM template is created. All other steps are skipped. This is useful for checking whether configured resource changes are possible (VM size available in target region? Disk properties compatible with VM size? Subscription quota sufficient? ...)
The following table explains the needed steps for disk creation in the target RG. "same az-user" means that parameters sourceSubUser = targetSubUser and sourceSubTenant = targetSubTenant.
| same az-user / same region | same az-user / different region | different az-user |
|---|---|---|
| - create snapshot * - create disk from snapshot |
- create incremental snapshot - copy snapshot to target - create disk from copied snapshot |
- create snapshot * - copy snapshot to BLOB - create disk from BLOB |
* Normally, a full snapshot is used. For UltraSSD_LRS and PremiumV2_LRS an incremental snapshot is used instead.
⚠️ Warning: RGCOPY uses the same name for incremental and full snapshots. When copying to a different region, an existing full snapshot cannot be used. Creating new snapshots can break parallel running RGCOPY runs that use the same source RG.
You can further change the behavior by setting the following parameters:
| parameter | [DataType]: usage |
|---|---|
useBlobCopy |
[switch]: Always use BLOB copy. This parameter is only needed for testing because BLOB copy is much slower and less reliable. |
useSnapshotCopy |
[switch]: Always use snapshot copy (even when source RG and target RG are in the same region). This parameter is only needed for testing. |
useIncSnapshots |
[switch]: Always use incremental snapshots rather than full snapshots |
createDisksManually |
[switch]: Do not use an ARM- or BICEP-template for creating disks (use New-AzDisk or a REST-API call instead) |
skipDiskCreation |
[switch]: Expect that all needed disks already exist in target RG |
justCopyDisks |
[switch]: Only copy disks to target RG. Do not deploy anything else in target RG. |
useRestAPI |
[switch]: Always Use REST-API calls instead of using Grant-AzSnapshotAccess, New-AzDisk and New-AzSnapshot (for snapshot copy) |
📝 Note:
UltraSSD_LRSandPremiumV2_LRSdisks can use a logical sector size of either 512 byte or 4 KB. A disk that is using a locical sector size of 512 byte can be converted to any SKU. However, a disk with a locical sector size of 4 KB can only be copied toUltraSSD_LRSorPremiumV2_LRS
If target RG and source RG are in the same region and the same user is used for both RGs then the BICEP template can use the snapshots in the source RG for creating the disks in the target RG.

For different regions, RGCOPY creates a temporary incremental snapshot in the target RG (as a copy of the incremental snapshot in the source RG).

You must use the same user and tenant for the accessing the source RG and target RG for using snapshot copy. When the subscriptions of the source RG and target RG are in different tenants then this is not possible. In this case, RGCOPY creates a temporary storage account for storing the disks content as BLOBs in the target RG.
⚠️ Warning: The storage account for BLOB copy has disabled storage account keys if the target subscription is a Microsoft internal subscription or when RGCOPY parametertargetNoSaKeysis set. In this case, the Azure user for the target subscription must have RBAC roleStorage Blob Data Contributor
If the target subscription is a Microsoft internal subscription then a Network Security Perimeter NSP in learning mode is created in the target RG.
By setting parameter justCopyDisks, RGCOPY only creates the disks in the target RG. No other Azure resources are deployed. Dependent on the target region and tenant, a snapshot copy or BLOB copy might be used.

By setting parameter skipDiskCreation, RGCOPY expects that all needed disks already exist in the target RG. This is useful if you want to use a different tool for copying (or replicating) the disks to the target RG.

As of version 0.9.50, Rgcopy is creating a BICEP template rather than an ARM template. Using BICEP has several advantages:
- You can copy a source RG with more than 200 resources. This is not possible when using ARM templates caused by a limitation of
Export-AzResourceGroup - BICEP always uses the newest API version for creating Azure resources. In ARM templates, you must define the API version manually.
- RGCOPY copies all supported resources from the source RG. In addition, it copies all supported resources that are directly or indirectly referenced by a virtual machine, even when these resources are located in a different resource group (This was not the case in RGCOPY versions that used ARM templates).However, all VMs and all disks must be located in the source RG. For example, a VM might be part of a Proximity Placement Group (PPG) that is stored in a different resource group. In this case, the VM and the PPG are both copied and will be stored in the target RG. However, it is not possible to copy 2 resources with the same type and same name (for example, a VM is using 2 network interface cards: a NIC with name NIC1 in resource group RG1 and a NIC with name NIC1 in resource group RG2).
- The created BICEP template is better readable compared with the ARM template. The ARM template contains more dependencies which caused some unexpected trouble, which is not the case anymore with BICEP.
RGCOPY automatically installs BICEP if it's not found.
The resource group parameters are essential for running RGCOPY:
| parameter | [DataType]: usage |
|---|---|
sourceRG |
[string]: name of the source resource group
|
targetRG |
[string]: name of the target resource group
📝 Note: The target resource group might already exist. However, it should not contain resources. For safety reasons, RGCOPY does not allow using a target resource group that already contains disks (unless you set switch parameter allowExistingDisks). |
targetLocation |
[string]: location name of the Azure region for the target RG, for example 'eastus'.
|
targetSA |
[string]: name of the storage account that will be created in the target RG for storing BLOBs. |
sourceSA |
[string]: name of the storage account that will be created in the source RG for storing file backups. This storage account is only created when parameter createVolumes or createDisks is set. |
📝 Note: Parameters
targetSAandsourceSAare normally not needed because RGCOPY is calculating them based on the name of the resource group. However, this could result in deployment errors because the storage account name must be unique in whole Azure (not only in the current subscription). Once you run into this issue, repeat RGCOPY and set these parameter to a unique name.
RGCOPY is using the current Azure Context (account and subscription) when no Azure Connection Parameter is provided. PowerShell caches the password of the Azure account inside the Azure Context for several hours. Therefore, you do not need to provide a password to RGCOPY. Simply run the following cmdlet just before RGCOPY:
Update-AzConfig -EnableLoginByWam $true
Connect-AzAccount `
-AuthScope 'Storage' `
-TenantId '7b5ebd57-e5fd-445f-a920-55897cd71921' `
-Subscription 'Subscription Name'The cmdlet opens the default browser and you can enter account name and password.
RGCOPY can use two different Azure accounts for connecting to the source RG and the target RG. In this case, you must run Connect-AzAccount for both accounts before starting RGCOPY. Furthermore, you must provide the RGCOPY connection parameters as described below. Hereby, RGCOPY knows which account has to be used for which resource group.
PowerShell caches the Azure context. However, the lifetime of the cache might be limited. Once the cache is expired, you must run Connect-AzAccount again.
💡 Tip: You should run
Connect-AzAccountimmediately before starting a copy to a different region (which might take several hours) because the cached credentials might expire during the runtime of RGCOPY.
Once this happens, yo do not need to start RGCOPY from scratch. There is an RGCOPY parameter that allows resuming in this particular case. See Parameters for Remote Copy
You can also use an Azure Managed System Identity (MSI) for running RGCOPY. Therefore, you have to create a VM (or container) with an MSI. Once you have assigned the required roles to the MSI and installed PowerShell and the Az module in the VM, you can run RGCOPY inside the VM. In this case, you must run the following command:
Connect-AzAccount -Identity -AccountId '<id>' -AuthScope Storage -Subscription '<name>'
After that, you can start RGCOPY without an RGCOPY Azure Connection Parameter.
Powershell cashes several Azure Contexts. Get-AzContext -ListAvailable shows all cached contexts. Get-AzContext shows the current context. When providing the below RGCOPY parameters then RGCOPY uses Set-AzContext for setting the current Azure Context. To be on the save side, you should always provide RGCOPY parameters sourceSub and sourceSubUser.
| parameter | [DataType]: usage |
|---|---|
sourceSub |
[string]: name of source subscription. Do not use the subscription id instead. |
sourceSubUser |
[string]: Azure account name (user, service principal or MSI) for source subscription. 📝 The account name for a Managed System Identity looks like this: MSI@0815. You can get the current account name by running Get-AzContext |
sourceSubTenant |
[string]: Azure tenant id for source subscription. 💡 This parameter is only needed if the user context is ambiguous without the tenant. |
targetSubtargetSubUsertargetSubTenant |
Same parameters as above but for the target subscription. Not needed if source and target subscription are identical |
With resource configuration parameters you can change properties of various resources in the Target ARM template.
Each of the configuration parameters has the following scheme:
[string] $parameter = "$rule1"
or [array] $parameter = @("$rule1","$rule2", ...)
or [boolean] $parameter # $True is converted to 'True', $False is converted to 'False'
or [int] $parameter # 1, 2, 3 ... are converted to '1', '2', '3' ...
with [string] $rule = "$configuration @ $resources"
[string] $resources = "$resourceName1, $resourceName2, ..."
[string] $configuration = "$part1 / $part2 / $part3"A resource configuration parameter is an array of strings. Each string represents a rule. Each rule has the form configuration@resources. Resources are separated by commas (,). A configuration might consist of up-to 3 parts separated by a slash (/).
Let's explain this for the parameter setVmSize which changes the size of VMs. In the examples, hana1 and hana2 are Azure resource names of virtual machines:
| setVmSize parameter value | result |
|---|---|
@("Standard_E32s_v3@hana1") |
changes the VM size of one VM (hana1) to Standard_E32s_v3 |
"Standard_E32s_v3@hana1" |
same as above but using a PowerShell string rather than an array |
'Standard_E32s_v3 @ hana1' |
same as above but using single quotes and separated by spaces |
'Standard_E32s_v3' |
changes the VM size of all VMs to Standard_E32s_v3 |
"Standard_E32s_v3 @ hana1, hana2" |
changes the VM size of 2 VMs (hana1 and hana2) to Standard_E32s_v3 (using one rule) |
@('Standard_E32s_v3 @ hana1', 'Standard_E16s_v3 @ hana2') |
changes the VM size for 2 VMs separately: Standard_E32s_v3 for hana1 and Standard_E16s_v3 for hana2 using 2 rules - therefore data type array is needed |
@("Standard_E16s_v3 @ hana2","Standard_E32s_v3") |
changes the VM size of hana2 to Standard_E16s_v3 and of all other VMs to Standard_E32s_v3. In this example, 2 rules fit for resource hana2. The more specific rule Standard_E16s_v3 @ hana2 wins. |
The following resource configuration parameters exist:
| parameter | usage (data type is always [string] or [array]) |
|---|---|
setVmSize =@("size @ vm1,vm2,...", ...) |
Set VM Size:
|
setDiskSku =@("sku @ disk1,disk2,...", ...) |
Set Disk SKU (default value is Premium_LRS).When setting to false (or $False or $Null), the disk SKU is not changed.
|
setDiskIOps = @("iops @ disk1,disk1,...", ...) |
Set Disk IOps:
|
setDiskMBps = @("mbps @ disk1,disk1,...", ...) |
Set Disk MBps:
|
setDiskSize = @("size @ disk1,disk1,...", ...) |
Set Disk Size:
setDiskTier instead. |
setDiskTier = @("tier @ disk1,disk1,...", ...) |
Set Disk Performance Tier:
|
setDiskBursting = @("bool @ disk1,disk2,...", ...) |
Set Disk Bursting:
|
setDiskMaxShares = @("number @ disk1,disk2,...", ...) |
Set maximum number of shares for a Shared Disk:
|
setDiskCaching = @("caching/wa @ disk1,disk2...", ...) |
Set Disk Caching:
|
setVmDeploymentOrder = @("prio @ vm1,vm2,...", ...) |
Set VM deployment Order:
|
setPrivateIpAlloc = @("allocation @ ip1,ip2,...", ...) |
Set Private IP Allocation Method:
|
removeFQDN = @("bool @ ip1,ip2,...", ...) |
Remove Fully Qualified Domain Names:
|
setAcceleratedNetworking = @("bool @ nic1,nic2,...", ...) |
Set Accelerated Networking:
|
createVolumescreateDiskssnapshotVolumes |
see section NetApp Volumes. |
ultraSSDEnabled |
[switch]: By default, VMs in the target RG only support Ultra SSD disks if such a disk is already attached. If you want to attach a new Ultra SSD disk later then you must set this RGCOPY switch when creating the target RG. |
RGCOPY uses default parameter values in Copy Mode. If you do not want this then set parameter switch skipDefaultValues or explicitly set the individual parameters to a different value. In Update Mode no default values are used. These are the default values in Copy Mode:
| parameter | default value | default behavior |
|---|---|---|
setDiskSku |
'Premium_LRS' | converts all disks to Premium_LRS if they originally were StandardSSD_LRS or Standard_LRS. |
setVmZone |
0 | removes zone configuration from VMs |
setPrivateIpAlloc |
'Static' | sets allocation of Private IP Addresses to Static |
setAcceleratedNetworking |
$True | enables Accelerated Networking |
removeFQDN |
$True | Removes the Fully Qualified Domain Name (even when skipDefaultValues is set) |
In addition, RGCOPY always performs the following changes:
- set the IP Allocation Method of Public IP Addresses to
Static - set the SKU of Public IP Addresses to
Standard - set the SKU of Load Balancers to
Standard
RGCOPY performs several consistency checks. Some of the found issues are automatically corrected by default. In this case, a warning is written into the RGCOPY log file. You should have a close look at this file to become aware of the (possibly unwanted) mediation of these issues. For example:
- Premium SSD disks are converted to Standard SSD disks if the VM size does not support Premium IO
- Accelerated Networking and Write Accelerator are disabled if not supported by the VM size
- Read-write caching is disabled if Write Accelerator is enabled
Some issues cannot be corrected by RGCOPY and result in an error, for example when:
- VM size does not exist in the target region
- Maximum number of NICs or disks is exceeds when changing the VM size
- Subscription quota for the VM size and region is not sufficient
You can change the default behavior of RGCOPY consistency checks using the following parameters:
| parameter | [DataType]: usage |
|---|---|
forceVmChecks |
[switch]: Do not automatically adjust any resource property |
skipVmChecks |
[switch]: Ignore any incompatible resource property 📝 Note: Normally, setting this parameter switch does not make any sense. When allowing incompatible resource properties then the deployment will fail. However, there is one scenario where this parameter is useful: RGCOPY relies on SKU information retrieved by Get-AzComputeResourceSku. If this information is wrong for any reason and you are sure that you know it better then you can set this parameter switch. |
simulate |
[switch]: Do not stop for (most of the) found consistency errors. 📝 Note: For each found error, RGCOPY writes a warning in red color. This is useful for detecting all errors by just running RGCOPY once. However, it is a simulation. You cannot copy a resource group while parameter simulate is set. |
| parameter | [DataType]: usage |
|---|---|
skipVMs |
[array] of VM names: These VMs and their disks are not copied by RGCOPY. 📝 Note: NICs that are bound only to these VMs and Public IP Addresses are skipped, too. However, NICs that are also bound to Load Balancers are still copied. |
takeVMs |
[array] of VM names: Only these VMs and their disks are copied by RGCOPY (opposite of parmeter skipVMs). |
skipDisks |
[array] of disk names: These disks are not copied by RGCOPY. |
skipSecurityRules |
[array] of name patterns: default value: @('SecurityCenter-JITRule*')Skips all security rules that name matches any element of the array. 📝 Note: By default, only Just-in-Time security rules are skipped (This is needed to avoid permanently opend ports in the target RG). All other security rules are copied. |
skipAvailabilitySetskipProximityPlacementGroup |
see Parameters for Availability |
skipBastion |
[switch]: do not copy Azure Bastion from source RG |
skipIdentities |
[switch]: By default, target VMs have the same (user assigned) managed identities assigned as the source VMs. You can skip this by using parameter skipIdentities. |
keepTags |
[array] of name patterns: default value: @('rgcopy*')Skips all Azure resource tags except the ones that name matches any element of the array. 📝 Note: By default, only Azure resource tags with a name starting with 'rgcopy' are copied. By setting parameter keepTags to @('*'), all Azure resource tags are copied. |
RGCOPY can change Availability Zones, Availability Sets and Proximity Placement Groups in the target RG. It does not touch the source RG configuration.
| parameter | [DataType]: usage |
|---|---|
setVmZone = @("zone @ vm1,vm2,...", ...) |
Set VM Availability Zone:
💡 Tip: Rather than 'none', you can use '0' for removing zone configuration. When setting to 'false', the existing zone is not changed. 💡 Tip: Disks are always created in the same zone as their VMs. Detached disks are only copied when parameter copyDetachedDisks was set. In this case, the detached disks are created in the zone that is defined by parameter defaultDiskZone (with default value 0). |
setVmFaultDomain = @("fault @ vm1,vm2,...", ...) |
Set VM Fault Domain:
|
skipVmssFlex |
[switch]: do not copy existing VM Scale Sets Flexible. Hereby, the target RG does not contain any VM Scale Set. |
skipAvailabilitySet |
[switch]: do not copy existing Availability Sets. Hereby, the target RG does not contain any Availability Set. |
skipProximityPlacementGroup |
[switch]: do not copy existing Proximity Placement Groups. Hereby, the target RG does not contain any Proximity Placement Group. |
createVmssFlex = @("vmss/fault/zones @ vm1,vm2,...", ...) |
Create a VMSS Flex (VM Scale Set with Flexible orchestration mode) for given VMs:
📝 Note: for zonal deployment, fault must have the value 1 (or none)1 or none).📝 Note: When not specifying any VM then this configuration is not valid for all VMs. Instead, a VMSS flex is created without any member. |
singlePlacementGroup |
Set property singlePlacementGroup for all VMSS Flex.Allowed values in { $Null, $True, $False}Setting this parameter is normally not needed. |
createAvailabilitySet = @("avset/fault/update @ vm1,vm2,...", ...) |
Create Azure Availability Set for given VMs:
📝 Note: When not specifying any VM then this configuration is not valid for all VMs. Instead, an AvSet is created without any member. |
createProximityPlacementGroup = @("ppg @ res1,res2,...", ...) |
Create Azure Proximity Placement Group for given resources:
📝 Note: When not specifying any resource then this configuration is not valid for all VMs. Instead, a PPG is created without any member. |
In Azure you cannot directly configure the Availability Zone for an Availability Set. However, you can indirectly pin an Availability Set to an Availability Zone. The trick is to deploy an additional VM that is in the Availability Zone. If this VM and the Availability Set are in the same Proximity Placement Group then they are also in the same Availability Zone. However, this only works if the VM is deployed first. If you deploy the Availability Set first then it might be deployed in a different Availability Zone. Afterwards, the deployment of the VM fails because the requirements for Availability Zone and Proximity Placement Group cannot be fulfilled at the same time. Luckily, you can define the deployment order in RGCOPY:
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
createAvailabilitySet = @(
'avset1/2/5 @ app1a, app1b',
'avset2/2/5 @ app2a, app2b')
setVmZone = @(
'1 @ hana1, ascs1',
'2 @ hana2, ascs2')
createProximityPlacementGroup = @(
'ppg1 @ ascs1, avset1',
'ppg2 @ ascs2, avset2')
setVmDeploymentOrder = @('1 @ ascs1, ascs2')
}
.\rgcopy.ps1 @rgcopyParameter
⚠️ Warning: RGCOPY ensures in this example that the VMs 'ascs1' and 'ascs2' are deployed before all other VMs. After stopping all VMs, the VMs in the Availability Sets are not bound to a Availability Zone anymore. Therefore, you have to take care on your own that 'ascs1' and 'ascs2' are always started before the other VMs in their Availability Sets.
An alternative for using Azure Availability Zones is using VMSS Flex (VM Scale Set with Flexible orchestration mode) with zones. Hereby, you can define the zone per VM. In each zone, Azure automatically distributes the VMs over different fault domains on best effort basis. This results in a mixture of zone deployment and using fault domains. Using Azure Availability Sets would not allow zones. Using Azure Availability Zones would not utilize fault domains.
Example of VMSS Flex with zones:
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
createVmssFlex = @(
'vmss/none/1+2 @ hana1, hana2, ascs1, ascs2, app1a, app1b, app2a, app2b'
)
setVmZone = @(
'1 @ hana1, ascs1, app1a, app1b',
'2 @ hana2, ascs2, app2a, app2b'
)
}
.\rgcopy.ps1 @rgcopyParameterWhen the source RG and the target RG are in different regions then the snapshots have to be copied into the target region first. This is running asynchronously in background and can take several hours (for disks with a size of some TiB).
💡 Tip: You should run
Connect-AzAccountimmediately before starting a copy to a different region because the cached credentials might expire during the runtime of RGCOPY.
You can restart RGCOPY during an async snapshot copy if az credentials become invalid or when PowerShell terminates (for example, when the PC is rebooting while RGCOPY was running). Therefore, you should start RGCOPY using the same parameters of the original run plus the additional parameter switch waitRemoteCopy
⚠️ Warning: You can only use parameterwaitRemoteCopyfor restarting RGCOPY during a snapshot copy or a BLOB copy. If RGCOPY terminates during snapshot creation completion or during disk creation completion then you have to start RGCOPY from the beginning.
When copying to a different tenant then RGCOPY is using BLOB copy rather than snapshot copy. You can force BLOB copy by using parameter useBlobCopy. However, this is not recommended because BLOB copy is slower and less reliable than snapshot copy.
It might happen that the snapshot copy or BLOB copy of a single disk fails after a few hours while all other disks have been copied successfully. In this case, you do not need to repeat the copy of all disks. In this case, you can do the following:
- In the target RG, delete all snapshots (or BLOBs) that have not been fully copied yet.
- Copy the missing snapshots (or BLOBs) manually by running RGCOPY with the parameter
justCopySnapshots(orjustCopyBlobs). The parameter is an array of (disks) names that have to be copied: Use the corresponding disk name, not the snapshot name or BLOB name. - Restart RGCOPY using the same parameters of the original run plus the additional parameter switch
skipRemoteCopy.
⚠️ Warning: all copied snapshots (and BLOBs) in the target RG are deleted by RGCOPY once the VM deployment in the target RG was successful.
The following parameters are typically not needed and only work with BLOB copy:
| parameter | [DataType]: usage |
|---|---|
grantTokenTimeSec |
[int]: Time in seconds, default value: 3*24*3600Before copying the BLOBs, access tokens are generated for the snapshots (or disks). These access tokens expire after 3 days. If the BLOB copy takes longer, then it fails. You can define a longer token life time using this parameter (before starting the BLOB copy). |
blobsRG |
[string], optional: resource group where the BLOBs are located |
blobsSA |
[string], optional: storage account where the BLOBs are located |
blobsSaContainer |
[string], optional: folder in storage account where the BLOBs are located |
justStopCopyBlobs |
[switch]: when set, the currently running BLOB copy is being terminated. Nothing else is done (no snapshots, no deployment). |
pathExportFolder is the default path for RGCOPY output files. The other parameters are full file paths of RGCOPY input files:
| parameter | [DataType]: usage |
|---|---|
pathExportFolder |
[string]: By default, RGCOPY creates all files in the user home directory. You can change the path for all RGCOPY files by setting parameter pathExportFolder. |
pathArmTemplate |
[string]: You can deploy an existing (main) ARM template by setting this parameter. No snapshots are created, no ARM template is created and no resource configuration changes are possible. |
| parameter | [DataType]: usage |
|---|---|
copyDetachedDisks |
[switch]: By default, only disks that are attached to a VM are copied to the target RG. By setting this switch, also detached disks are copied. |
maxDOP |
[int]: RGCOPY performs the following operations in parallel:
maxDOP. |
jumpboxName |
[string]: When setting a jumpboxName, RGCOPY adds a Full Qualified Domain Name (FQDN) to the Public IP Address of the jumpbox. The FQDN is calculated from the name of the target RG. 📝 Example: targetRG=test_resource_group and targetLocation=eastusresults in FQDN: test-resource-group.eastus.cloudapp.azure.com. RGCOPY uses the first Public IP Address of the first VM which fits the search for *jumpboxName* |
justCreateSnapshots |
[switch]: When setting this switch, RGCOPY only creates snapshots on the source RG (no ARM template creation, no deployment). This is useful for refreshing the snapshots for an existing ARM template. You can use parameter useIncSnapshots in addition for creating incremental snapshots rather than full snapshots. |
justDeleteSnapshots |
[switch]: When setting this switch, RGCOPY only deletes snapshots on the source RG (no ARM template creation, no deployment). Caution: you typically want to keep the existing snapshots since ARM templates within the same region refer to these snapshots. |
By default, RGCOPY does not copy storage accounts. However you can copy storage accounts including FILE and BLOB services when specifying new names for the storage accounts using parameter renameSa. For example:
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
# COPY STORAGE ACCOUNT AZURE RESOURCES
renameSa = @(
'newSaName1 @ saName1',
'newSaName2 @ saName2'
)
# copy storage ALL account content
copySaShares = $true
# alternatively: define shares to copy
# copySaShares = @(
# 'container'
# 'nfs'
# 'smb'
# )
# OPTIONAL PARAMETER: Subnet ID if control plane subnet
subnetIdControlPlane = $null
}
.\rgcopy.ps1 @rgcopyParameterThis will copy the storage accounts saName1 and saName2 from the source RG to the target RG and rename them to newSaName1 and newSaName2. Other storage accounts in the source RG will not be copied.
- All source storage accounts (source SA) must be located in the source RG. The target SAs will be created in the target RG. If source RG and target RG are in the same subscription then a single Azure user can be used for source and target.
- You must use parameter
renameSato define the names of the target SAs. You cannt use the names of the source SAs since storage account names must be unique in whole Azure.
📝 Note: Mounting the shares inside the VMs and changing
/etc/fstabhas to be done manually after running RGCOPY.
📝 Note: RGCOPY does not copy private endpoints. Granting network access for the storage accounts from the VMs has to be done manually in the target RG.
You can copy all BLOBs in containers as well as all files in SMB and NFS file shares by setting parameter copySaShares. This uses the tool azcopy which is called by RGCOPY.
📝 Note: For Storage account copy, it is recommended running RGCOPY inside an Azure VM with user assigned managed identity. In this case, a subnet rule is created from the VM's subnet to the storage accounts. RGCOPY tries ro figure out the subnet on its own. To be on the save side, you can use RGCOPY parameter
subnetIdControlPlanefor setting the subnet ID. In this subnet, the service endpointMicrosoft.Storage.Globalmust be enabled.
⚠️ Warning: When starting RGCOPY from a PC, authentication to the storage accounts might fail the first time. In this case, you have to runconnect-AzAccounta second time. Therefore, storage account copy with starting RGCOPY from a PC makes only sense if you use parameterjustCopySaShares(for repeating storage account copy).
You can even copy BLOBs and files between different tenants. For this, you need two Azure users (one per tenant) having the Required RBAC roles (see below).
📝 Note: Copying files of NFS or SMB shares between between different tenants is only possible if either the source SA or the target SA is configured allowing SA keys.
In this scenario, let's call the tenant that contains the storage account with allowed SA keys tenantKey, and the other tenant tenantOther. For storage account copy, you should follow these steps:
- Add a user assigned managed identity to the control plane VM. This managed identity must be in tenantOther.
- Run
connect-AzAccount -Identity -AccountId <id> -AuthScope Storage -SubscriptionName '<name>'for tenantOther. - Run
connect-AzAccount -DeviceAuth -AuthScope Storage -SubscriptionName '<name>'for tenantKey. - Start RGCOPY in the control plane VM.
The storage accounts might or might not be associated to a network security perimeter (NSP) in Learning Mode. NSPs in Enforced Mode are not supported by RGCOPY yet.
📝 Note: If you want to copy the storage account content then you must enable Public Network Access (for selected networks) in the source SA before starting RGCOPY. This can be configured independently from an associated NSP.
RGCOPY changes the following storage account configurations in the source RG:
- RGCOPY creates a subnet rule for all storage accounts in the source RG to allow network access for the control plane VM (VM that runs RGCOPY).
- When running on a PC, RGCOPY creates either a storage account IP rule or an NSP IP rule for the IP address of the PC.
RGCOPY changes the following storage account configurations in the target RG:
allowSharedKeyAccessThis will be set to False for Microsoft internal subscriptions. For other subscriptions, it will be set to True (as long as RGCOPY parametertargetNoSaKeysis not set).publicNetworkAccessThis configuration will only be changed if it wasSecuredByPerimeterin the source SA. In this case, you will get a warning in RGCOPY and the target SA will be created withpublicNetworkAccess = Disabled.networkAcls.defaultActionThis will always be set to Deny in the target RG.- RGCOPY creates a subnet rule for all storage accounts in the target RG to allow network access for the control plane VM (VM that runs RGCOPY).
- When running on a PC, RGCOPY creates either a storage account IP rule or an NSP IP rule for the IP address of the PC.
- If the target RG is in a Microsoft internal subscription then RGCOPY automatically creates an NSP in the target RG and associates all copied storage accounts to this NSP in learning mode.
The users that are used for accessing the source subscription and target subscription must have the following RBAC roles (in addition to Contributor):
- when copying blobs:
Storage Blob Data Contributor - when copying files from SMB shares:
Storage File Data SMB Share Elevated Contributor - when copying files from NFS shares:
Storage File Data Privileged Contributor
These RBAC roles should be set on subscription level to grant access for the source RG and target RG. However, resource group level is sufficient for the source RG.
RGCOPY calls azcopy and defines the authentication type for the source and target storage account separately. One of the following authentication types is used (in this order):
- Token using storage account key
If the storage account is configured to allow storage account keys then RGCOPY creates an SAS token using storage account key 1 (see parameter
copySaKeyNamebelow) - User delegation token For BLOB containers, RGCOPY tries to create a user delegation SAS token. This is only possible if the RBAC roles mentioned above are set.
- OAuth authentication
If an SAS token cannot be created then OAuth authentication is used. When running RGCOPY in an Azure VM, the same manged identity is taken that was used for running
connect-AzAccount -Identity -AccountId <id> -AuthScope Storage -SubscriptionName '<name>'
You can directly copy from a file share or copy from a file share snapshot that has been created by RGCOPY when using parameter switch copySaUsingSnapshots.
⚠️ Warning: SMB permissions are not copied when using a snapshot. NFS permissions are copied, whether a snapshot is used or not.
📝 Note: Using BLOB snapshots is not possible with RGCOPY.
Azure file share snapshots (SMB and NFS) do not have a name. They only have a creation date. There can only be one RGCOPY file share snapshot per share. RGCOPY stores the names of its snapshot as Azure tags in the storage account. This is needed to distinguish between RGCOPY snapshots and other (manual) snapshots of a file share.
The storage account might have the following Azure tags:
rgcopySnapshot_nfs = 2025-10-08T12:28:33ZrgcopySnapshot_smb = 2025-10-08T12:28:37Z
This means that the file share with name nfs has a snapshot created by RGCOPY with the name 2025-10-08T12:28:33Z. Another snapshot exists for a file share with the name smb.
Before RGCOPY creates a new snapshot, it delets its previous snapshot and the corresponding Azure tag. Once the new RGCOPY snapshot is created, its name is stored as an Azure tag in the storage account resource.
You might manually delete an RGCOPY file share snapshot using Azure portal. However, there is no feature implemented in RGCOPY to delete an old RGCOPY file share snapshot (rather than creating a new one).
RGCOPY file share snapshots are only created when using parameter copySaUsingSnapshots and not setting parameter skipSnapshots. The latter parameter skips creating file share snapshots as well as disk snapshots.
RGCOPY uses the tool azcopy to copy the content of storage accounts. This might fail for any reason. In this case, you can simply repeat RGCOPY by adding the parameter switch justCopySaShares (and adjusting parameter copySaShares). This will skip creating snapshots and deploying the BICEP template (However, a new BICEP template will be created).
| parameter | usage |
|---|---|
renameSa = @("newSaName @ oldSaName", ...) |
Set the names of of the new storage accounts:
|
copySaShares |
[boolean] or [array]: Copies storage account content (BLOBs and files) after deploying target RG. When set to true, all containers and shares are copied. When passing an array of names, all containers and shares include in this array are copied. |
subnetIdControlPlane |
[string]:Subnet ID of control plane (VM that runs RGCOPY), for example: /subscriptions/5b0f1c1f-e257-4872-a1e3-bf4ad6f452e7/resourceGroups/control_plane/providers/Microsoft.Network/virtualNetworks/vnet-name/subnets/defaultThis subnet will be granted to all copied storage accounts. RGCOPY tries to figure out the subnet ID on its own. If this does not work then you have to set parameter subnetIdControlPlane manually. |
justCopySaShares |
[switch] Just copy the BLOBs and files defined by parameter copySaShares. Do not create snapshots and do not deploy anything. |
copySaUsingSnapshots |
[switch]: Create file share snapshots and use them as the source when copying file shares. |
copySaKeyName |
[string]: If storage account key should be used for azcopy then you can define here which key. allowed: key1, key2default: key1 |
copySaRevokeCpAccess |
[switch]: Revoke access from control plane VM after content has been copied. |
copySaEnvironment |
[hashtable]: Environment variables and their value that are set when azcopy is running. Default value is:@{ AZCOPY_DISABLE_SYSLOG = 'true' NO_PROXY = '*'} |
In Azure, you cannot export the snapshot of a NetApp volume to a BLOB or restore it in another region. Therefore, RGCOPY cannot directly copy NetApp volumes. However, RGCOPY supports NetApp volumes on LINUX using file copy (rather than disk or volume copy). Hereby, the following scenarios are possible:
| source RG | target RG | procedure |
|---|---|---|
| Disks | NetApp volumes NFSv4.1 | skip disks and create new volumes |
| NetApp volumes | Premium SSD disks | create new Premium SSD disks |
| NetApp volumes | NetApp volumes NFSv4.1 | create new volumes |
For the source RG, RGCOPY must know the mount points inside the VMs for all disks and volumes. Hereby, RGCOPY can backup all files that are stored in these mount points to an NFS share in the source RG. In the target RG, new disks or volumes are created for these mount points. After that, RGCOPY restores the files from the NFS share to the mount points in the target RG.
⚠️ Warning: Unlike other RGCOPY features, File Copy requires running code inside the source RG and the target RG. Therefore, the stability of this feature depends on the OS and other running software inside the VMs. Using this feature is on your own risk. To be on the save side, you should use database backup and restore rather than converting the database disks using RGCOPY.
📝 Note: RGCOPY currently uses
tarfor File Copy which is quite slow during verify. Future versions of RGCOPY will replacetarwith a differnt tool.
There are several restrictions for using this feature:
- Requirements for the source VMs:
- NFS client must be installed
/etc/fstabmust be in specific format:nofailmust be set for all disks and mount points. When skipping disks, the device names/dev/sd*might change. Therefore, you should use the Azure specific device names/dev/disk/azure/scsi1/lun*-part*instead.- For NetApp, the NFSv4 domain name should be set to
defaultv4iddomain.com, see https://learn.microsoft.com/en-us/azure/azure-netapp-files/azure-netapp-files-configure-nfsv41-domain
- Requirements in Azure
- All NetApp volumes in the source RG must be configured to allow snapshot access inside the VMs.
- In the target RG, only a single NetApp account and a single volume pool is created. All VMs that access NetApp volumes in the target RG must use the same subnet for accessing NetApp volumes. This subnet must already exist in the source RG and use delegation for
Microsoft.NetApp/volumes. You must define this subnet using RGCOPY parametersubnetNetApp. - In the source RG, all VMs involved in file copy must use a second subnet with disabled Private Endpoint Network Policies (typically the default subnet). You must define this subnet using RGCOPY parameter
subnetEndpoint.
- Consistent backup (keep content of different mount points in sync)
- During file backup and restore, no open files must exists in the copied mount points. You should disable automatically starting services like SAP and databases.
- In most cases, the VMs must be stopped to create snapshots from disk. For the file copy, the VMs have to be started again. File copy from NetApp volumes uses NetApp snapshots. However, file copy from disks never uses snapshots. Therefore, the content of disks, copied mount points from disks and copied mount points from NetApp volumes might not be in sync in the target VMs.
- Do never forget to set parameter
snapshotVolumeswhen copying mount points from NetApp volumes
The following RGCOPY parameters are available:
| parameter | usage |
|---|---|
skipDisks = @('diskName1', ...) |
[array] of disk names: These disks are not copied by RGCOPY./etc/fstab for all disks (not only the skipped disks) as described above. |
createVolumes = @("size @ mp1,mp2,...", ...) |
Create new NetApp volumes in the target RG
|
createDisks = @("size @ mp1,mp2,...", ...)@("size/iops/mbps@mp1,mp2,...", ...) |
Create new disks in the target RG
|
snapshotVolumes = @("account/pool @ vol1,vol2...", ...)@("rg/account/pool @ vol1,vol2...", ...) |
Create NetApp volume snapshots in the source RG
sourceRG |
subnetEndpoint='<vnet>/<subnet>' |
[string]: Existing subnet in source RG that can be used for RGCOPY NFS share during file copy.
|
subnetNetApp='<vnet>/<subnet>' |
[string]: Existing subnet in source RG that will be used for NetApp volumes during file copy. |
When using file copy of mount points the following happens:

- For each volume (parameter
snapshotVolumes), the NetApp snapshotrgcopyis deleted and created again. - The stortage account with the name
rgcopy<sourceRG>is created in the source RG. Alternatively, you can define the name using parametersourceSA. The NFS file sharergcopyis created in the storage account. A private endpoint is created in the NFS subnet (parametersubnetEndpoint) of the source RG. - A backup script is started in each involved VM in the source RG. This script mounts the NFS share and backups the content of the mount points (parameters
createDisks,createVolumes) to the NFS share. After that, the NFS share is unmounted (you can mount it again using bash script/mnt/mntrgcopy.sh). - The BICEP template contains new disks and volumes (parameters
createDisks,createVolumes) and skips disks (parameterskipDisks). After deploying the BICEP template in the target RG, a private endpoint is created in the NFS subnet of the target RG. - A restore script is started in each involved VM in the target RG. It partitions, formats and mounts newly created disks and changes
/etc/fstab. After that, it mounts the NFS share and restores the files to the mount points (parameterscreateDisks,createVolumes). Then the NFS share is unmounted. - The private endpoints to the NFS share in the target RG are deleted again.
- Neither the private endpoints to the NFS share in the source RG nor the NFS share and storage account are deleted in the source RG (as long as you do not set parameter
deleteBackups). If you want to delete them later, you can start RGCOPY again with parameterdeleteBackupsOnly.
You can use the following scenarios:
The NetApp volumes used in the source RG can be located in any NetApp account and capacity pool. You might use different NetApp accounts in different resource groups. In the target RG, RGCOPY creates a single NetApp account with a single capacity pool that contains all volumes for the target RG. The size of the volumes in the target RG does not need to be the same as in the source RG.
When copying a NetApp volume, you must use two RGCOPY parameters:
snapshotVolumes=@("rg/account/pool@vol1,vol2...", ...)
This parameter specifies all NetApp volumes that are used in the source RG. It results in performing NetApp volume snapshots with the name 'rgcopy'. Existing snapshots with this name will be overwritten. You have to specify the resource group (rg), the NetApp account (account), the capacity pool (pool) and all volume names (vol1, vol2, ...). You can skip the resource group (rg) if the NetApp account is in the source RG.createVolumes=@("size@,mp1,mp2,...", ...)
This parameter specifies all mount points (mp1, mp2, ...). A mount point has the format vmName/path. For each mount point, RGCOPY backups the files in the source RG, creates and mounts a new NetApp volume in the target RG and restores the files. You have to specify the size of the new NetApp volume (size) in GiB. The minimum size is 100 GiB.
In this example, three volumes are in the source RG and one volume in resource group remoteRG. In VM hanadb, 3 mount points exist: /hana/data and /hana/log with 1 TiB and /test with 128 GiB:
snapshotVolumes = @(
'account/pool1@vol1, vol2, vol3',
'remoteRG/accountRem/poolRemote@vol4'
)
createVolumes = @(
'1024@hanadb/hana/data, hanadb/hana/log',
'128@hanadb/test'
)
⚠️ Warning: RGCOPY backups all files from the path<mountPoint>/.snapshot/rgcopy/*This is the directory for the snapshot with the name 'rgcopy'. If this snapshot directory does not exists then RGCOPY backups the files in<mountPoint>/*
Not setting parametersnapshotVolumesresults in using an outdated snapshot and in inconsistent data in the target RG.
This works similar to copying NetApp volumes. However, you do not need parameter snapshotVolumes here. Instead, you need:
skipDisks=@("diskName1", ...)
Hereby, you specify the disks in the source RG that contained the data of the mount points. As a result, all these disks are not copied. No snapshot for these disks is created.createVolumes=@("size@,mp1,mp2,...", ...)
See parameter description above.
Example:
skipDisks = @(
'hanaData',
'hanaLog'
)
createVolumes = '1024 @ hanadb/hana/data, hanadb/hana/log'This also works similar to copying NetApp volumes. Here, you need the following parameters:
snapshotVolumes=@("rg/account/pool@vol1,vol2...", ...)
See parameter description above.createDisks=@("size@,mp1,mp2,...", ...)
This parameter works just the same ascreateVolumes. However, it creates a new premium SSD disk in the target RG rather than a NetApp volume. The size of the disk can be very small (even 1 GiB) as long as all files of the mount point fit into the new disk.
Example:
snapshotVolumes = 'anfAccount/anfPool@anfVolume1, anfVolume2'
createDisks = @(
'1024 @ vmName/volumes/mount1',
'512 @ vmName/volumes/mount2'
)In the scenarios above, you could change the storage type (disk or volume) and size. However, you were not able to change the number of mount points. However, RGCOPY also supports the following scenario: Assume, you are using 3 disks for the 3 SAP HANA mount points:
- /hana/data
- /hana/log
- /hana/shared
You might want to convert these 3 disks to a single NetApp volume using mount point /hana. Doing this, will allow you performing NetApp volume snapshots that are consistent over all 3 directories. You can implement such scenarios by performing 3 steps:
- Run RGCOPY with parameters
snapshotVolumes,createVolumes,createDisksandskipDisksaccording to your needs as described above. In addition, set parameterstopRestore. Hereby, RGCOPY stops before performing the file restore. All disks and volumes have already been created in the target RG, but they are not mounted yet. New disks are not partitioned and not formatted yet. - Mount the disks and volumes on your own. Partition and format the additional disks. Change the file
/etc/fstabaccordingly. - Start RGCOPY again using exactly the same parameters as in step 1 with one exception: Use parameter
continueRestorerather thanstopRestore. This results in restoring the files and performing all following RGCOPY steps.
You can use RGCOPY for creating the target RG in a different region. Therefore, disks have to be copied to the target region. The runtime depends on the size of the disks. When creating a new disk or volume using backup/restore as described above, the runtime does not depend on the disk/volume size. It depends on the total size of all files inside the disk/volume. Therefore, it might be a good idea deleting unneeded files before copying a resource group. In particular for databases, you can decrease the total file size (and RGCOPY runtime) by deleting archive log files or unneeded database backups.
| parameter | [DataType]: usage |
|---|---|
netAppAccountName |
[string]: Name of the created NetApp Account in the target RG. Default is rgcopy-<targetRG> |
netAppServiceLevel |
[string]:Service Level of the created NetApp Pool. Allowed values: Standard, Premium, Ultra. Default is Premium |
netAppNetworkFeatures |
[string]:Network Features for NetApp Volumes. Allowed values: Standard, Basic. Default is Standard |
netAppPoolName |
[string]: Name of the created NetApp Pool. Default is rgcopy-s-pool, rgcopy-p-pool, rgcopy-u-pool (for Service Level Standard, Premium, Ultra) |
netAppPoolGB |
[int]: Size of the created NetApp Pool. Default value is 4096 📝 Note: RGCOPY creates a larger NetApp pool if the sum of all volumes is larger than 4096 GiB. Using this parameter you can increase the capacity pool size in the target RG, even if the size of all created volumes is less than 4096 GiB. |
createDisksTier |
[string]: By default, disks created by RGCOPY parameter createDisks have the minimum performance tier 'P20' to speed-up backup/restore on small disks. You can change the minimum performance tier to any value between 'P2' and 'P50' using parameter createDisksTier |
nfsQuotaGiB |
[int] Maximum size of the temporary RGCOPY NFS share. Default value is 5120. |
waitBackup |
[switch]: You can restart RGCOPY using this additional switch if RGCOPY has been terminated while waiting for file backup to finish. |
waitRestore |
[switch]: You can restart RGCOPY using this additional switch if RGCOPY has been terminated while waiting for file restore to finish. |
In Clone mode, one or more VMs are cloned within the same resource group. Hereby, a new VM is created using a copy of the disks and having the same configuration as the original VM. The OS name of original and clone is identical. However, new names are created for the Azure resources (VM, Disk, NIC and Public IP Address). The clone can be part of none, the same, or a different Availability Zone, Availability Group, Proximity Placement Group and VMSS Flex. Original and clone are attached to the same virtual subnet. This is possible since the following changes are done:
- The original VM is stopped
- An Azure Read Only resource lock is created that prevents starting the original VM
- Private IP Addresses of the clone are changed to dynamic
Use cases for Clone Mode are for example:
- Creating a copy of an application server
After cloning an application Server, the following manual steps are needed;- renaming the cloned VM on OS level, setting new static IP addresses (if wanted), updating application configuration (e.g. SAP profiles)
- removing the Read Only resource lock and starting the original VM
- Changing availability configuration
You might want to move from an Availability Set or Proximity Placement group to a VMSS Flex. Therefore, you normally have to delete the VM and re-create it with the new availability configuration. This might fail for any reason (e.g. quota issue). As a result, the original VM is away.
When using RGCOPY with Clone Mode, the original VM still exists until you manually delete it after deploying and testing the clone. If the cloning fails then you can manually remove the Read Only lock and start the original VM.
The following parameters can be set in Clone Mode:
| parameter | [DataType]: usage |
|---|---|
cloneMode |
[switch]: Turns on Clone Mode. |
cloneVMs |
[array]: Names of the VMs that will be cloned. |
setVmName = @("vmNameClone @ vmNameOriginal", ...) |
Set the names of the cloned VMs:
The names of the cloned VMs are calculated automatically when not using setVmName. The calculated names end with -clone<number> (-clone1, -clone2...). RGCOPY searches for a free clone number that can be used for all cloned VMs, disks, NICs and Public IP Addresses. By setting RGCOPY parameter cloneNumber you can define the starting point of this search. |
attachVmssFlex = @("vmssFlexName @ vmNameOriginal", ...) |
By default, the cloned VMs are detached from their Virtual Machine Scale Set. However, you can attach them to the same or a different VM Scale Set (compared with the original VM) using this parameter:
|
attachAvailabilitySet = @("avSetName @ vmNameOriginal", ...) |
By default, the cloned VMs are detached from their Availability Set. However, you can attach them to the same or a different Availability Set (compared with the original VM) using this parameter:
|
attachProximityPlacementGroup = @("ppgRG/ppgName @ vmNameOriginal", ...)attachProximityPlacementGroup = @("ppgName @ vmNameOriginal", ...) |
By default, the cloned VMs are detached from their Proximity Placement Group. However, you can attach them to the same or a different Proximity Placement Group (compared with the original VM) using this parameter:
|
setVmSizesetVmZonesetVmFaultDomainsetDiskSizesetDiskTiersetDiskBurstingsetDiskCachingsetDiskSku |
Same parameters as in Copy Mode. They are described in section Resource Configuration Parameters. Be aware that these parameters only have an impact on the newly created resources in the sourceRG. Already existing resources are not modified (except setting a ReadOnly lock on the cloned VMs). |
Example for cloning application servers and attaching them to a new VMSS Flex
# create VMSS Flex Config
$paramConfig = @{
Location = 'eastus'
OrchestrationMode = 'Flexible'
PlatformFaultDomainCount = 1
Zone = @('1', '2')
}
$vmssConfig = New-AzVmssConfig @paramConfig
# create VMSS Flex
$paramVmss = @{
ResourceGroupName = 'sap_test'
VMScaleSetName = 'vmssZone'
VirtualMachineScaleSet = $vmssConfig
}
New-AzVmss @paramVmss
# clone VMs
$rgcopyParameter = @{
sourceRG = 'sap_test'
cloneMode = $True
cloneVMs = @(
'appserver1'
'appserver2'
)
setVmZone = @(
'1 @ appserver1'
'2 @ appserver2'
)
attachVmssFlex = 'vmssZone'
}
.\rgcopy.ps1 @rgcopyParameterIn Merge mode, one or more VMs are merged into another resource group. Hereby, a new VM is created using a copy of the disks. A single NIC is created and attached to the virtual subnet that is defined by parameter setVmMerge. If the original VM has at least one public IP address then a single public IP address is created for the new VM.
The following limitations exist in Merge Mode:
- The target RG must already exist and contain the virtual subnets that are defined by parameter
setVmMerge - The VM name on OS level is not changed
- Source RG and target RG are typically different (indeed, the two RGs could be identical. However, in this case you should rather use Clone Mode)
Merge Mode can be used, for example, for copying a jumpbox from one resource group to a different subnet in another resource group.
The following parameters can be set in Merge Mode:
| parameter | [DataType]: usage |
|---|---|
mergeMode |
[switch]: Turns on Merge Mode. |
setVmMerge= @("net/subnet@vm1,vm2", ...) |
[string] or [array]: Merge VMs of the source RG into an existing subnet of the target RG:
|
setVmNameattachVmssFlexattachAvailabilitySetattachProximityPlacementGroupsetVmSizesetVmZonesetVmFaultDomainsetDiskSizesetDiskTiersetDiskBurstingsetDiskCachingsetDiskSku |
Same parameters as in Clone Mode. |
The following example copies VMs 'app1' and 'app2' from the source RG ('source_rg') and merges them into the subnet 'vnet/subnet' of the target RG ('target_rg'). The VM names in the target RG are changed to 'appserver1' and 'appserver2' and the availablilty zone for these VMs is set in the target RG. Keep in mind that RGCOPY changes only the Azure resource names of these VMs. The names on OS level are not changed!
$rgcopyParameter = @{
sourceRG = 'source_rg'
targetRG = 'target_rg'
targetLocation = 'eastus'
MergeMode = $True
setVmMerge = @(
'vnet/subnet @ app1'
'vnet/subnet @ app2'
)
setVmName = @(
'appserver1 @ app1'
'appserver2 @ app2'
)
setVmZone = @(
'1 @ app1'
'2 @ app2'
)
}
.\rgcopy.ps1 @rgcopyParameter| feature | Copy Mode | Merge Mode | Clone Mode |
|---|---|---|---|
| deployment | target RG only | target RG (target RG might be same as source RG) |
source RG only |
| virtual network | a copied VM is attached to a copied subnet in the target RG that has the same name as the original subnet in the source RG |
a merged VM can be attached to any subnet that exists in the target RG |
a cloned VM and the original VM are always attached to the same subnet |
| changes in source RG |
snapshots |
|
|
| resource names |
|
|
|
Availability resources
|
|
|
see Merge Mode |
| Availability Zone | removed by default (can be changed using setVmZone) |
copied by default (can be changed using setVmZone) |
see Merge Mode |
| Disk SKU | set to Premium_LRS by default (can be changed using setDiskSku) |
copied by default (can be changed using setDiskSku) |
see Merge Mode |
You can activate RGCOPY Update Mode by setting parameter switch updateMode. In this mode, you can update resource properties in the source RG. No ARM template is created. No target RG is created or even used. Therefore, parameters targetRG and targetLocation are not allowed in this mode. Be aware that the RGCOPY log file name also changes in Update Mode, see section Created Files.
A simple example of this mode looks like this:
$rgcopyParameter = @{
sourceRG = 'contoso_source_rg'
updateMode = $True
setDiskSku = 'Standard_LRS'
}
.\rgcopy.ps1 @rgcopyParameterThe following special parameters can be set in Update Mode:
| parameter | [DataType]: usage |
|---|---|
updateMode |
[switch]: Turns on Update Mode |
simulate |
[switch]: You can use this switch for checking the status of the source RG. Once this switch is set, nothing is changed in the source RG. Instead, the expected changes are displayed. |
stopVMsSourceRG |
[switch]: When setting this switch, RGCOPY stops all VMs in the source RG. Be aware, that all VMs must be stopped anyway when using Update Mode. |
setVmSizesetDiskSizesetDiskTiersetDiskCachingsetDiskSkusetDiskBurstingsetDiskMaxSharessetAcceleratedNetworking |
Same parameters as in Copy Mode. However, this time they are used for changing the source RG using Az cmdlets. The parameters are described in section Resource Configuration Parameters. You can combine all these parameters. RGCOPY will update all required resources (VMs, disks, NICs). When there are several changes of a single resource then the resource will only be updated once (containing all changes). |
deleteSnapshots |
[switch]: When setting this switch, RGCOPY deletes snapshots with the extension '.rgcopy'. These snapshots have been originally created by RGCOPY. |
deleteSnapshotsAll |
[switch]: When setting this switch, RGCOPY deletes all snapshots in the source RG. |
createBastion ='<addrPrefix>@<vnet>' |
Create Bastion:
|
deleteBastion |
[switch]: When setting this switch, RGCOPY deletes the following resources in the source RG:
|
netAppServiceLevel |
[string] allowed: Standard, Premium, Ultra When setting this parameter in Update Mode, the Service Level of existing NetApp Pools can be changed. For one pool after the other, a new pool is created using the new Service Level, all volumes are moved to the new pool, finally the old pool is deleted. This does not happen for pools that already have the required Service Level. 📝 Note: For using this feature, you must enable the dynamically change of NetApp Service Levels for your subscription. This is described at https://docs.microsoft.com/en-us/azure/azure-netapp-files/dynamic-change-volume-service-level |
netAppMovePool |
[string] Pool name in the format <account>/<pool>When setting this parameter, Service Level changes only happens for this given pool. All other pools are not touched by parameter netAppServiceLevel |
netAppMoveForce |
[switch] Parameter for test purposes When setting this switch, volumes are moved to a new pool even when the Service Level already fits parameter netAppServiceLevel |
netAppPoolName |
[string] in Update mode: Name of the newly created pool if parameter netAppMovePool is also set.By default, the created pool has the name rgcopy-s1-<old-pool>, rgcopy-p1-<old-pool>, rgcopy-u1-<old-pool> (for Service Level Standard, Premium, Ultra).If the name already exists then the number is increased, for example rgcopy-s2-my_old_pool_name. |
Detached disks are not ignored in Update Mode. There is no explicit parameter for excluding disks (like skipDisk or skipVMs). You can update all disks or explicitly specify the disk you want to update. Not specified disks are not processed. For example:
$rgcopyParameter = @{
sourceSub = 'Contoso Subscription'
sourceRG = 'contoso_source_rg'
updateMode = $True
# stopVMsSourceRG = $True
# simulate = $True
# update 3 disks: disk_lun_0, disk_lun_1, disk_lun_3
SetDiskTier = @(
'P40 @ disk_lun_0, disk_lun_1',
'P30 @ disk_lun_3'
)
# turn on write accelerator for one disk (write_acc_disc)
# turn off WA and enable ReadOnly cache for all other disks
SetDiskCaching = @(
'None/True @ write_acc_disc',
'ReadOnly/False'
)
# update all disks and NICs
setDiskSku = 'Premium_LRS'
setAcceleratedNetworking = $True
}
.\rgcopy.ps1 @rgcopyParameterFor reducing cost of a resource group that is not in use (all VMs stopped), you could run the following script:
$rgcopyParameter = @{
sourceSub = 'Contoso Subscription'
sourceRG = 'contoso_source_rg'
updateMode = $True
deleteBastion = $True
setDiskSku = 'Standard_LRS'
deleteSnapshotsAll = $True
}
.\rgcopy.ps1 @rgcopyParameterBy using parameter justCopyDisks, you can copy all or specific disks from the source RG to the target RG. This includes detached disks. No other resources are deployed in the target RG.
When setting this parameter, disk snapshots are created in the source RG. If needed, snapshots are copied to the target RG an deleted afterwards.
The zone property of the disks is also copied. If you want to deploy the disks in the target RG in a different zone then you must set parameter defaultDiskZone. This parameter is applied to all disks. Setting it to 0 will remove zonal deployment. However, disks of SKU UltraSSD_LRS or PremiumV2_LRS will always use zonal deployment.
Example 1: copy all disks
$rgcopyParameter = @{
sourceRG = 'contoso_source_rg'
targetRG = 'contoso_target_rg'
targetLocation = 'eastus'
justCopyDisks = $True
}
.\rgcopy.ps1 @rgcopyParameterExample 2: copy specific disks to zone 1
$rgcopyParameter = @{
sourceRG = 'contoso_source_rg'
targetRG = 'contoso_target_rg'
targetLocation = 'eastus'
justCopyDisks = @('disk1', 'disk2')
defaultDiskZone = 1
}
.\rgcopy.ps1 @rgcopyParameterExample 3: copy all disks without zonal deployment
$rgcopyParameter = @{
sourceRG = 'contoso_source_rg'
targetRG = 'contoso_target_rg'
targetLocation = 'eastus'
justCopyDisks = @('disk1', 'disk2')
defaultDiskZone = 0
}
.\rgcopy.ps1 @rgcopyParameterWhen a disk with the same name already exists in the target RG then you can use parameter defaultDiskName to copy a single disk and rename it. For example:
$rgcopyParameter = @{
sourceRG = 'contoso_source_rg'
targetRG = 'contoso_target_rg'
targetLocation = 'eastus'
justCopyDisks = @('disk1')
defaultDiskZone = 0
defaultDiskName = 'disk1_newName'
}
.\rgcopy.ps1 @rgcopyParameterRGCOPY can start scripts in specific scenarios. These are either locally running scripts (PowerShell scripts running on the same machine that runs RGCOPY) or remotely running scripts (scripts running inside the VMs). These scripts have to be developed on your own. RGCOPY continues once the scripts have finished. The ouptut of the scripts is contained in the RGCOPY log file (when using Write-Output or echo).
In addition, RGCOPY starts scripts for backup/restore. These scripts are part of RGCOPY and cannot be changed. They are used for copying NetApp Volumes.
RGCOPY passes the following parameters to your scripts:
- All supplied RGCOPY parameters.
- Some of the optional RGCOPY parameters, even when not supplied. For example
targetSub. - Parameter
sourceLocationthat contains the region of the source RG - Parameter
vmNamethat contains the name of the VM that is running the script (or your local machine name for locally running scripts). - Parameter
vmTypethat contains the value of the VM tagrgcopy.VmTypeof the VM that is running the script. - Parameters
vmSize<vmName>,vmCpus<vmName>,vmMemGb<vmName>that contain Azure VM size configuration for all deployed VMs.<vmName>is the Azure name of the VM that only contains word characters and numbers. All special characters are removed. - Parameter
rgcopyParametersthat contains the names of all passed parameters.
In all scripts you can simply access the passed parameters using variables, for example $targetSub. Only remotely running PowerShell scripts must contain a param clause.
RGCOPY uses Invoke-AzVMRunCommand for remotely running scripts. RGCOPY terminates with an error message if the output of such a script contains the text ++ exit 1 within the last lines. Be aware that Invoke-AzVMRunCommand does only return the last few dozen lines of stdout and stderr.
RGCOPY can start local PowerShell scripts in the following two scenarios. These scripts have to be developed on your own. An example of such a script is examplePostDeployment.ps1.
| parameter | [DataType]: usage |
|---|---|
pathPostDeploymentScript |
[string]: path to local PowerShell script You can use this script for deploying additional ARM resources that cannot be exported from the source RG. When using this RGCOPY parameter, the following happens after deploying the ARM templates (in RGCOPY step deployment):
|
pathPreSnapshotScript |
[string]: path to local PowerShell script When using this RGCOPY parameter, the following happens:
|
For the following scenarios, remotely running scripts (running inside the VMs) can be started by RGCOPY. For Windows VMs, the scripts must be PowerShell scripts. For LINUX VMs, the scripts must be Shell scripts. Examples of such scripts are exampleStartAnalysis.ps1 and exampleStartAnalysis.sh.
| parameter | [DataType]: usage |
|---|---|
scriptStartSapPath ='[local:]<path>@<VM>[,...n]' |
[string]: Runs a script for starting the SAP system (database and NetWeaver), for example '/root/startSAP.sh @ sapserver'
'su - sidadm -c startsap @ sapserver'💡 Tip: The script is started using PowerShell cmdlet Invoke-AzVMRunCommand. This will fail if the script does not finish within roughly half an hour. Therefore, you cannot use this for long running tasks (as an SAP benchmark). In this case, you must write a script that triggers or schedules the long running task and finishes without waiting for the task to complete. |
scriptStartLoadPath ='[local:]<path>@<VM>[,...n]' |
[string]: Runs a script for starting SAP Workload (SAP benchmark). Same details apply here as for parameter scriptStartSapPath above. |
scriptStartAnalysisPath ='[local:]<path>@<VM>[,...n]' |
[string]: Runs a script for starting Workload Analysis. Same details apply here as for parameter scriptStartSapPath above. |
startWorkload |
[switch]: Enables the last step of RGCOPY: Workload and Analysis. This switch enables the RGCOPY step Start Workload. In this step, the following is performed:
startWorkload in addition. |
vmStartWaitSec |
[int]: Wait time in seconds, default value: 5 * 60After starting the VMs, RGCOPY gives the VMs some time to become fully operational. This delay might be needed for starting all services (for example, SSH service) inside the VM. |
vmAgentWaitMinutes |
[int]: Maximum wait time in minutes, default value: 30Before running Invoke-AzVMRunCommand, RGCOPY waits until the Azure Agent status is 'Ready'. This is checked every minute. If the status is still not 'Ready' after the maximum wait time then RGCOPY gives up and terminates with an error. |
⚠️ Warning: For remotely running scripts, RGCOPY uses the cmdletInvoke-AzVMRunCommandthat connects to the Azure Agent running inside the VM. Make sure that you have installed a recent version of the Azure Agent. See also https://docs.microsoft.com/en-US/troubleshoot/azure/virtual-machines/support-extensions-agent-version.
Invoke-AzVMRunCommand expects that the script finishes within roughly one hour. If the script takes longer then Invoke-AzVMRunCommand (and RGCOPY) terminates with "Long running operation failed". If you want to use longer running scripts then you must write a wrapper script that just triggers or schedules your original script. The wrapper script can then be started using RGCOPY.
RGCOPY writes the working directory of Invoke-AzVMRunCommand to stdout respectively stderr (and the RGCOY log file), for example /var/lib/waagent/run-command/download/4. You might double check the log files in this directory once Invoke-AzVMRunCommand fails with a timeout.
For starting SAP, you must write your own script. This script must contain systemctl start sapinit if you are using NetApp volumes. The path of the script has to be specified using parameter scriptStartSapPath (see above). This script will be started by RGCOPY in the following cases:
- In the source RG: before running the local script specified by parameter
pathPreSnapshotScript - In the target RG: before running the local script specified by parameter
pathPostDeploymentScript - In the target RG: at the beginning of step Workload and Analysis (if parameter
startWorkloadis set)
If more than one case applies in the target RG then SAP will only be started once.
For example, using a Post-Deployment-Script works like this:
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
scriptStartSapPath = '/root/startSAP.sh @ SAPAPP'
pathPostDeploymentScript = 'c:\scripts\PostDeploymentScript.ps1'
}
.\rgcopy.ps1 @rgcopyParameterIf you just want to start SAP without implementing a Post-Deployment-Script, you can simply pass an invalid path for pathPostDeploymentScript, for example:
$rgcopyParameter = @{
sourceRG = 'SAP_master'
targetRG = 'SAP_copy'
targetLocation = 'westus'
scriptStartSapPath = '/root/startSAP.sh@SAPAPP'
pathPostDeploymentScript = 'dummy'
}
.\rgcopy.ps1 @rgcopyParameterRGCOPY automatically installs on Linux the VM extension AzureMonitorLinuxAgent and on Windows AzureMonitorWindowsAgent.
You can skip this by using RGCOPY switch parameter skipExtensions. When parameter autoUpgradeExtensions is set then the extensions will automatically upgrade in the future.
In addition, you can install the following extensions:
| parameter | [DataType]: usage |
|---|---|
installExtensionsSapMonitor |
[array]: Names of VMs for deploying the SAP Monitor Extension. Alternatively, you can set the Azure tag rgcopy.Extension.SapMonitor for the VM. If you do not want to install the SAP Monitor Extension although the Azure tag has been set, use switch ignoreTags. |
By default, RGCOPY does not delete all its intermediate storage (snapshots in source RG, file backups). This can save a lot of time when regularly copying the same resource group. However, the intermediate storage results in Azure charges.
The following parameters activate additional steps at the very end of an RGCOPY run:
| parameter | [DataType]: usage |
|---|---|
deleteSnapshots |
[switch]: By setting this switch, RGCOPY deletes those snapshots in the source RG that have been created by the current run of RGCOPY. When skipping some VMs or disks, RGCOPY does not create snapshots of these disks and does not delete them afterwards. |
deleteBackups |
[switch]: By setting this switch, RGCOPY deletes the storage account in the source RG that has been used for storing file backups (during the copy process of NetApp volumes). |
stopVMsTargetRG |
[switch]: When setting this switch in Copy Mode, RGCOPY stops all VMs in the target RG after deploying it. Typically, this is not what you want. However, it might be useful for saving costs when deploying a resource group that is not used immediately. |
Tip: You can use the following RGCOPY parameters for reducing cost in the target RG: setVmSize, setDiskSku, setDiskTier, createDisksTier, netAppServiceLevel, netAppPoolGB and skipBastion.
The default values of some RGCOPY parameters also have some cost impact. See parameters createDisksTier and setDiskSku above.
The behavior of RGCOPY changed for copying NetApp volumes. It now starts only needed VMs in the source RG. These VMs are stopped again by RGCOPY. In earlier versions of RGCOPY all VMs were started in the source RG and you had to stop them on your own.
RGCOPY copies the following resources from the source RG. All other resources in the source Resource RG are skipped and not copied to the target RG:
- Microsoft.Compute/virtualMachines
- Microsoft.Compute/disks
- Microsoft.Network/virtualNetworks
- Microsoft.Network/networkSecurityGroups
- Microsoft.Network/networkInterfaces
- Microsoft.Network/publicIPAddresses
- Microsoft.Network/publicIPPrefixes
- Microsoft.Network/loadBalancers
- Microsoft.Network/natGateways
- Microsoft.Compute/availabilitySets
- Microsoft.Compute/proximityPlacementGroups
- Microsoft.Compute/virtualMachineScaleSets
- Microsoft.Network/bastionHosts
- Microsoft.Storage/storageAccounts/fileServices
- Microsoft.Storage/storageAccounts/blobServices
In the target RG, the following ARM resources might be deployed in addition:
- Microsoft.Compute/virtualMachines/extensions
- Microsoft.NetApp/netAppAccounts
- Microsoft.Compute/images
Not all properties of the resources are copied, for example
- The DNS server property of NICs is not copied. The old DNS server would not be accessible anyway in the target RG because the virtual network in the target RG is isolated.
- Network peering is not copied.
- RGCOPY can only copy VMs that are located in the same region as the source RG.
- RGCOPY creates snapshots of all disks in the source RG with the name <diskname>.rgcopy.
- if BLOB copy is used, then RGCOPY grants access to the snapshots at the beginning and revokes this access at the end of the BLOB copy.
- If RGCOPY parameter
snapshotVolumesis supplied, then snapshots of NetApp volumes with the name rgcopy are created. - If RGCOPY parameter
createVolumesorcreateDisksis supplied, then a storage account with a premium NFS share is created in the source RG. All needed VMs are started (and stopped later) in the source RG. In these VMs, the NFS share /mnt/rgcopy is mounted. The storage account will not be deleted again unless you use RGCOPY parameterdeleteSourceSA. - If RGCOPY parameter
pathPreSnapshotScriptis supplied, then the specified PowerShell script is executed before creating the snapshots. In this case, all VMs are started, SAP is started, the PowerShell script (located on the local PC) is executed and finally all VMs are stopped in the source RG
⚠️ Warning: Snapshots of disks are made independently. However, database files could be distributed over several data disks. Using these snapshots for creating a VM could result in inconsistencies and database corruptions in the target RG. Therefore, RGCOPY cannot copy VMs with more than one data disk while the source VM is running. However, RGCOPY does work with running VMs that have only a single data disk (and no NetApp volume) or a single NetApp volume (and no data disk).
In the unlikely case that database files are distributed over the data disk (or volume) and the OS disk, you must stop the VM before starting RGCOPY. RGCOPY does not (and cannot) double check this unlikely case.
⚠️ Warning: When using NetApp volumes, RGCOPY does not know which volume belongs to which VM. Therefore, you must specify the volume snapshots using RGCOPY parametersnapshotVolumes. Not doing so results in using an outdated snapshot and inconsistent VM in the target RG.
⚠️ Warning: RGCOPY can convert a managed disk in the source RG to a NetApp volume in the target RG (and vice versa) by changing mount points. Herby, a file backup is made from the mount points in the source RG. A mount point is either a disk or a NetApp volume.
Before starting the backup/restore, RGCOPY double checks that there is no open file in the mount point directory. However, it does not check this during backup/restore. Therefore, you must you must make sure that no LINUX service or job that changes files in the mount point directories is started during backup/restore.
It is not allowed, running multiple instances of RGCOPY at the same time for deploying/changing the same target RG. However, running multiple instances of RGCOPY using the same source RG is possible with the following restrictions:
- Each parallel running RGCOPY instance must have its own working directory. This can be forced by setting a different value for parameter
pathExportFolderfor each RGCOPY instance (or by running the different RGCOPY instances on different PCs). - The source RG must not be changed. Therefore:
- snapshots must not be created (use parameter
skipSnapshots) - the following parameters are not allowed:
snapshotVolumes,createVolumes,createDisks, andpathPreSnapshotScript
- snapshots must not be created (use parameter
- The source RG and all target RGs must be in the same region. For copying to a different region, a snapshot copy is used which changes the status of the snapshots in the source RG (the incremental snapshot in the source RG is no longer the latest snapshot).
RGCOPY does not double check whether another instance of RGCOPY is running. When running multiple instances of RGCOPY in parallel, you must take care of the restrictions on your own.
RGCOPY creates the following files in the user home directory (or in the directory which has been set using RGCOPY parameter pathExportFolder) on the PC where RGCOPY is running:
| file | [DataType]: usage |
|---|---|
rgcopy.<source_RG>.SOURCE.json |
Exported template from the source RG |
rgcopy.<target_RG>.TARGET.jsonrgcopy.<source_RG>.TARGET.jsonrgcopy.<target_RG>.TARGET.biceprgcopy.<target_RG>.DISKS.bicep |
Generated template files created by RGCOPY (dependent on used RGCOPY mode) |
rgcopy.<target_RG>.TARGET.logrgcopy.<source_RG>.SOURCE.log |
Standard RGCOPY log file (dependent on used RGCOPY mode) |
rgcopy.txt |
Backup of the running script rgcopy.ps1 used for support |
rgcopy.<target_RG>.<time>.ziprgcopy.<source_RG>.<time>.zip |
Compressed ZIP file that contains all files above (dependent on used RGCOPY mode) |
rgcopy.<target_RG>.TEMP.jsonrgcopy.<target_RG>.TEMP.txt |
temporary files |
For starting a workload test you need two things:
- A source RG with the workload (VMs containing SAP System and script for starting SAP)
- The deployment tool (RGCOPY)
RGCOPY tags are used to decouple these two parts. For example, the path of the SAP start script should not be part of the deployment (RGCOPY parameter scriptStartSapPath). It should rather be part of the workload. You can achieve this by setting the Azure Tag rgcopy.ScriptStartSap on any VM in the source RG. Keep in mind that this is a tag of a VM , not a tag of the resource group.
With RGCOPY Azure TAgs you can impact the RGCOPY behavior. These tags will also be copied to the VMs in the target RG. The tags are evaluated by RGCOPY as long as parameter switch ignoreTags is not set:
| virtual machine tag | [DataType]: usage |
|---|---|
rgcopy.DeploymentOrder |
[int]: When not setting parameter setVmDeploymentOrder, the value of the tag is used to define the deployment order of the VM (that has the tag) |
rgcopy.Extension.SapMonitor |
[string]: When not setting parameter installExtensionsSapMonitor and setting the tag to 'true', the Azure Enhanced Monitoring Extension for SAP will be installed on the vm (that has the tag). |
rgcopy.VmType |
[string]: When starting s script from RGCOPY using parameters scriptStartSapPath, scriptStartLoadPath or scriptStartAnalysisPath then the value of this tag is contained in variable vmType. Hereby, the script can behave differently dependent on the VM where it has been started. |
The following 3 tags must contain the VM name in their value (for example, tag rgcopy.ScriptStartSap with value /root/startSAP.sh@vm2). Therefore, they can be set on any VM. However, you should not set the same tag using different values on different VMs.
| virtual machine tag | [DataType]: usage |
|---|---|
rgcopy.ScriptStartSap |
[string]: Sets the parameter scriptStartSapPath to the value of the tag if the parameter is not already explicitly set (and ignoreTags is not set). |
rgcopy.ScriptStartLoad |
[string]: Sets the parameter scriptStartLoadPath to the value of the tag if the parameter is not already explicitly set (and ignoreTags is not set). |
rgcopy.ScriptStartAnalysis |
[string]: Sets the parameter scriptStartAnalysisPath to the value of the tag if the parameter is not already explicitly set (and ignoreTags is not set). |
In addition, RGCOPY writes the following two tags. These are tags of the Resource Group while all other tags above are tags of the Virtual Machine
| resource group tag | [DataType]: usage |
|---|---|
Owner |
[string]: Default value is targetSubUser.You can set the tag to any value by using parameter setOwner.When setting this parameter it to $Null, no "Owner" tag will be created. |
Created_by |
[string]: This tag is set to 'rgcopy.ps1' in the target RG. |
You can easily read all Azure VM tags of a resource group using the PowerShell script tag-get.ps1:
# tag-get.ps1
#Requires -Version 7.0
param (
[Parameter(Mandatory = $True, Position = 0)] [string]$resourceGroup,
[Parameter(Mandatory = $False, Position = 1)] [string]$vmName # single VM only
)
$parameter = @{
ResourceGroupName = $resourceGroup
}
if ($vmName.length -ne 0) {
$parameter.Add('Name', $vmName)
}
$vms = Get-AzVM @parameter
$allTags = @()
$vms | ForEach-Object {
[hashtable] $tags = $_.Tags
foreach($tag in $tags.getenumerator()) {
$row = @{
vm = $_.Name
tag = $tag.Name
value = $tag.value
}
[array] $script:allTags += $row
}
}
$allTags `
| Select-Object vm, tag, value `
| Sort-Object vm, tag `
| Format-TableYou can set Azure VM tags of a resource group using the PowerShell script tag-set.ps1:
# tag-set.ps1 $resourceGroup $vmName @('tag1=value1', 'tag2=value2', ...)
#Requires -Version 7.0
param (
[Parameter(Mandatory = $True, Position = 0)] [string] $resourceGroup,
[Parameter(Mandatory = $True, Position = 1)] [string] $vmName,
[Parameter(Mandatory = $True, Position = 2)] $tags,
[switch] $removeOldTags
)
# get old tags
$vm = Get-AzVM -ResourceGroupName $resourceGroup -Name $vmName
# remove old tags
if ($removeOldTags -eq $True) {
$vm.Tags.Clear()
}
# process new tags
foreach ($tag in $tags) {
$tagKey,$tagValue = $tag -split '='
if (($tagKey.length -ne 0) -and ($tagValue.length -ne 0)) {
if ($tagValue -eq '$Null') {
$vm.Tags.Remove($tagKey) | Out-Null
}
else {
$vm.Tags.$tagKey = $tagValue
}
}
}
# set new tags
$res = Set-AzResource `
-ResourceGroupName $resourceGroup `
-Name $vmName `
-ResourceType 'Microsoft.Compute/VirtualMachines' `
-Tag $vm.Tags `
-Force
# output of new tags
$allTags = @()
[hashtable] $tagsHash = $res.Tags
foreach($t in $tagsHash.getenumerator()) {
$row = @{
vm = $vm.Name
tag = $t.Name
value = $t.value
}
[array] $script:allTags += $row
}
$allTags `
| Select-Object vm, tag, value `
| Sort-Object vm, tag `
| Format-Table
m, tag `
| Format-TableThe following script sets the RGCOPY tags for a whole resource group:
param (
$resourceGroup
)
tag-set.ps1 $resourceGroup vm1 'rgcopy.DeploymentOrder=1'
tag-set.ps1 $resourceGroup vm2 'rgcopy.DeploymentOrder=2'
tag-set.ps1 $resourceGroup vm3 'rgcopy.DeploymentOrder=2'
tag-set.ps1 $resourceGroup vm1 'rgcopy.Extension.SapMonitor=true'
tag-set.ps1 $resourceGroup vm2 'rgcopy.ScriptStartSap=/root/startSAP.sh@vm2'Azure is validating an ARM template as the first step of an deployment. This validation might fail for various reasons. In this case, you can see the errors in the output of RGCOPY (on the host and in the RGCOPY log file). RGCOPY performs several checks (including quota of VM families) before starting the deployment. The screenshot below is from a deployment that explicitly turned off RGCOPY quota checks (using RGCOPY switch skipVmChecks)
If the ARM template validation succeeds but errors occur during deployment then you can check details of the deployment errors in the Azure Portal.





