Skip to content

Fix Remove-rsv.ps1 script#221

Open
SvenAelterman wants to merge 15 commits into
mainfrom
216-remove-rsvps1-script-fails-to-complete
Open

Fix Remove-rsv.ps1 script#221
SvenAelterman wants to merge 15 commits into
mainfrom
216-remove-rsvps1-script-fails-to-complete

Conversation

@SvenAelterman

Copy link
Copy Markdown
Collaborator

No description provided.

@SvenAelterman SvenAelterman linked an issue Dec 30, 2025 that may be closed by this pull request
@SvenAelterman
SvenAelterman marked this pull request as ready for review January 30, 2026 19:22
Copilot AI review requested due to automatic review settings January 30, 2026 19:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes issues with the Remove-rsv.ps1 PowerShell script that removes Azure Recovery Services Vaults, along with related improvements to Bicep templates and other scripts.

Changes:

  • Updated Recovery Services Vault API version to 2025-08-01 and added async operation polling logic
  • Commented out code sections in Remove-rsv.ps1 that no longer work due to Azure policy changes (soft delete cannot be disabled, ASR operations fail)
  • Improved error handling in Remove-Spoke.ps1 with better resource existence checks and exit codes
  • Fixed variable naming consistency from {rgname} to {rgName} in Bicep templates
  • Added safe navigation operators for conditional module outputs in Bicep
  • Removed temporary Az.Resources version restriction from deployment scripts

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
scripts/PowerShell/Scripts/Recovery/Remove-rsv.ps1 Major refactoring: commented out soft delete and ASR cleanup code that no longer works, added async operation polling logic for vault deletion, updated API version
scripts/PowerShell/Scripts/Remove-Spoke.ps1 Improved error handling with resource existence checks, better warning messages, and exit code on errors
shared-modules/recovery/recoveryServicesVault.bicep Updated vault API version to 2025-08-01, fixed deployment name length to 64 characters, added comment about future user-assigned identity support
research-spoke/main.bicep Fixed variable naming from {rgname} to {rgName}, added safe navigation operators for conditional modules, added BCP334 linter suppressions, commented out unused import
research-spoke/deploy.ps1 Removed temporary Az.Resources version restriction
research-hub/deploy.ps1 Removed temporary Az.Resources version restriction
.vscode/settings.json Added new words to spell checker dictionary
.vscode/launch.json Added PowerShell debug configuration for Remove-rsv.ps1 script
.gitignore Added AzPortalRefScript folder to ignore list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .vscode/launch.json Outdated

# Derive AzRestMethod path from full URL; need to remove 'https://management.azure.com' prefix but not the `/`
# https://learn.microsoft.com/powershell/module/az.accounts/invoke-azrestmethod?view=azps-15.1.0#-path
$AzRestMethodPath = $OperationUrl -replace "^$((Get-AzContext).Environment.ResourceManagerUrl)", "/"

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex replacement on line 277 removes the ResourceManagerUrl but then adds back a leading slash. However, if the ResourceManagerUrl from Get-AzContext doesn't end with a slash but the OperationUrl doesn't have a slash after the domain, this could result in '//' or missing path. Consider verifying the regex pattern handles all cases correctly, or use a more explicit string manipulation approach like: '$OperationUrl.Substring((Get-AzContext).Environment.ResourceManagerUrl.Length)'

Suggested change
$AzRestMethodPath = $OperationUrl -replace "^$((Get-AzContext).Environment.ResourceManagerUrl)", "/"
$ctx = Get-AzContext
$resourceManagerUrl = $ctx.Environment.ResourceManagerUrl.TrimEnd('/')
if ($OperationUrl.StartsWith($resourceManagerUrl, [System.StringComparison]::OrdinalIgnoreCase)) {
$AzRestMethodPath = $OperationUrl.Substring($resourceManagerUrl.Length)
}
else {
# Fallback: if the OperationUrl does not start with the expected ResourceManagerUrl, use it as-is
$AzRestMethodPath = $OperationUrl
}
# Ensure AzRestMethod path starts with a single leading slash
if (-not $AzRestMethodPath.StartsWith('/')) {
$AzRestMethodPath = '/' + $AzRestMethodPath
}

Copilot uses AI. Check for mistakes.
Comment thread scripts/PowerShell/Scripts/Recovery/Remove-rsv.ps1

#Write-Host "Warning: This script will only remove the replication configuration from Azure Site Recovery and not from the source. Please cleanup the source manually. Visit https://go.microsoft.com/fwlink/?linkid=2182781 to learn more." -ForegroundColor Yellow

foreach ($item in $pvtEndpoints) {

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable naming inconsistency: The variable is defined as '$pvtendpoints' (lowercase) on line 75, but referenced as '$pvtEndpoints' (camelCase) on line 177. PowerShell is case-insensitive so this will work, but it's inconsistent. The variable should use the same casing throughout for better code maintainability.

Suggested change
foreach ($item in $pvtEndpoints) {
foreach ($item in $pvtendpoints) {

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest a fix on line 75 instead as line 177 uses the correct casing

Comment thread research-spoke/main.bicep Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 20, 2026 16:21
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.

Comment thread scripts/PowerShell/Scripts/Recovery/Remove-rsv.ps1
Comment on lines +282 to +288
# [int]$PollIntervalSec = 0 # base interval
# if ($headers.Contains("Retry-After")) {
[int]$PollIntervalSec = [int]${headers.GetValues("Retry-After")}?[0] ?? 20
# }
# else {
# $PollIntervalSec = 20
# }
Comment on lines +297 to +298
$opResponse = Invoke-AzRestMethod -Method GET -Path $AzRestMethodPath -ErrorAction SilentlyContinue

Write-Host "`n❌ An error occurred: $($_)"
Write-Host $_.ScriptStackTrace
Write-Host "In context $AzContext"
Write-Host "In subscription: $($AzContext.Subscription.Id) - $($AzContext.Subscription.Name)"
Comment thread .vscode/launch.json Outdated
Comment on lines +44 to +46
# HACK: 2025-11-16: svaelter: Can no longer disable soft delete in any region for a Recovery Services Vault.
# Ref: https://learn.microsoft.com/azure/backup/secure-by-default?tabs=preview#supported-scenarios
#Set-AzRecoveryServicesVaultProperty -Vault $VaultToDelete.ID -SoftDeleteFeatureState Disable #disable soft delete
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 21, 2026 20:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

scripts/PowerShell/Scripts/Recovery/Remove-rsv.ps1:181

  • Variable casing is inconsistent: $pvtendpoints is declared earlier, but the loop uses $pvtEndpoints. PowerShell is case-insensitive so it works, but it reduces readability and makes it easier to miss typos. Consider renaming the declaration to $pvtEndpoints (or revert the loop) so the casing is consistent throughout the script.
foreach ($item in $pvtEndpoints) {
	$penamesplit = $item.Name.Split(".")
	$pename = $penamesplit[0]
	Remove-AzPrivateEndpointConnection -ResourceId $item.Id -Force #remove private endpoint connections
	Remove-AzPrivateEndpoint -Name $pename -ResourceGroupName $ResourceGroup -Force #remove private endpoints

Comment on lines 33 to +42
Select-AzSubscription $SubscriptionId -Tenant $Tenant | Out-Null
$VaultToDelete = Get-AzRecoveryServicesVault -Name $VaultName -ResourceGroupName $ResourceGroup
Write-Verbose "Selected Recovery Services Vault: $($VaultToDelete.Name) in Resource Group: $($VaultToDelete.ResourceGroupName)`n$($VaultToDelete.ID)"
# Ignore WhatIfPreference here because future cmdlets will fail without this being set
# This should have no side effects
Set-AzRecoveryServicesAsrVaultContext -Vault $VaultToDelete -WhatIf:$false
# Set-AzRecoveryServicesAsrVaultContext -Vault $VaultToDelete -WhatIf:$false
#Set-AzRecoveryServicesVaultContext -Vault $VaultToDelete #-WhatIf:$false

$UpdatedVault = Update-AzRecoveryServicesVault -ResourceGroupName $VaultToDelete.ResourceGroupName -Name $VaultToDelete.Name -ImmutabilityState "Disabled"
Write-Host "Immutability state set to $($UpdatedVault.Properties.ImmutabilitySettings.ImmutabilityState)"
Write-Host "Immutability state set to '$($UpdatedVault.Properties.ImmutabilitySettings.ImmutabilityState)'."
Comment on lines 36 to 40
# Ignore WhatIfPreference here because future cmdlets will fail without this being set
# This should have no side effects
Set-AzRecoveryServicesAsrVaultContext -Vault $VaultToDelete -WhatIf:$false
# Set-AzRecoveryServicesAsrVaultContext -Vault $VaultToDelete -WhatIf:$false
#Set-AzRecoveryServicesVaultContext -Vault $VaultToDelete #-WhatIf:$false

# if ($ASRProtectedItems -ne 0) { Write-Host $ASRProtectedItems "ASR protected items are still present in the vault. Remove the same for successful vault deletion." -ForegroundColor Red }
# if ($ASRPolicyMappings -ne 0) { Write-Host $ASRPolicyMappings "ASR policy mappings are still present in the vault. Remove the same for successful vault deletion." -ForegroundColor Red }
# if ($fabricCount -ne 0) { Write-Host $fabricCount "ASR Fabrics are still present in the vault. Remove the same for successful vault deletion." -ForegroundColor Red }
if ($pvtendpointsFin.count -ne 0) { Write-Host $pvtendpointsFin.count "Private endpoints are still linked to the vault. Remove the same for successful vault deletion." -ForegroundColor Red }
Comment on lines +282 to +288
# [int]$PollIntervalSec = 0 # base interval
# if ($headers.Contains("Retry-After")) {
[int]$PollIntervalSec = [int]${headers.GetValues("Retry-After")}?[0] ?? 20
# }
# else {
# $PollIntervalSec = 20
# }
Comment on lines +294 to +308
while ((Get-Date) -lt $Deadline) {
$Attempt++

$opResponse = Invoke-AzRestMethod -Method GET -Path $AzRestMethodPath -ErrorAction SilentlyContinue

# If the polling request returns 404 Not Found, it means that the resource is deleted
if (404 -eq $opResponse.StatusCode) {
# Some services return 404 Not Found when the resource is deleted
Write-Host "DELETE operation succeeded (resource not found)."
break
}

# Attempt to parse JSON; handle cases where content may be empty or non-JSON
$opJson = $opResponse.Content | ConvertFrom-Json -ErrorAction SilentlyContinue

Comment thread .vscode/launch.json
Comment on lines +14 to +20
"<your-resource-group>",
"-ResourceGroup",
"<your-vault-name>",
"-VaultName",
"<your-subscription-id>",
"-SubscriptionId",
"YOUR_SUBSCRIPTION_ID",
Write-Host "`n❌ An error occurred: $($_)"
Write-Host $_.ScriptStackTrace
Write-Host "In context $AzContext"
Write-Host "In subscription: $($AzContext.Subscription.Id) - $($AzContext.Subscription.Name)"
…null

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 21, 2026 20:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

scripts/PowerShell/Scripts/Recovery/Remove-rsv.ps1:224

  • $pvtEndpointsFin is assigned here but referenced later as $pvtendpointsFin (different casing). PowerShell is case-insensitive so it works, but keeping a single casing throughout will reduce confusion (especially when similarly-named variables exist).
$pvtEndpointsFin = Get-AzPrivateEndpointConnection -PrivateLinkResourceId $VaultToDelete.ID

Comment thread .vscode/launch.json
Comment on lines +14 to +20
"<your-resource-group>",
"-ResourceGroup",
"<your-vault-name>",
"-VaultName",
"<your-subscription-id>",
"-SubscriptionId",
"YOUR_SUBSCRIPTION_ID",
Comment on lines 40 to +43
# Ignore WhatIfPreference here because future cmdlets will fail without this being set
# This should have no side effects
Set-AzRecoveryServicesAsrVaultContext -Vault $VaultToDelete -WhatIf:$false
# Set-AzRecoveryServicesAsrVaultContext -Vault $VaultToDelete -WhatIf:$false
#Set-AzRecoveryServicesVaultContext -Vault $VaultToDelete #-WhatIf:$false
Comment on lines +286 to +292
# [int]$PollIntervalSec = 0 # base interval
# if ($headers.Contains("Retry-After")) {
[int]$PollIntervalSec = [int]${headers.GetValues("Retry-After")}?[0] ?? 20
# }
# else {
# $PollIntervalSec = 20
# }
Comment on lines +279 to +281
# Derive AzRestMethod path from full URL; need to remove 'https://management.azure.com' prefix but not the `/`
# https://learn.microsoft.com/powershell/module/az.accounts/invoke-azrestmethod?view=azps-15.1.0#-path
$AzRestMethodPath = $OperationUrl -replace "^$((Get-AzContext).Environment.ResourceManagerUrl)", "/"
Comment on lines +301 to +302
$opResponse = Invoke-AzRestMethod -Method GET -Path $AzRestMethodPath -ErrorAction SilentlyContinue


#Write-Host "Warning: This script will only remove the replication configuration from Azure Site Recovery and not from the source. Please cleanup the source manually. Visit https://go.microsoft.com/fwlink/?linkid=2182781 to learn more." -ForegroundColor Yellow

foreach ($item in $pvtEndpoints) {
Write-Host "`n❌ An error occurred: $($_)"
Write-Host $_.ScriptStackTrace
Write-Host "In context $AzContext"
Write-Host "In subscription: $($AzContext.Subscription.Id) - $($AzContext.Subscription.Name)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove-rsv.ps1 script fails to complete Remove Recovery Services Vault script needs to be updated to match the latest Portal-generated model

3 participants