Azure doesn’t provide a built-in method for migrating Key Vault Secrets other than the basic resource mover, which is limited to moving entire Key Vaults between Resource Groups and Subscriptions.
If the resource mover works for you then great, but what if you only want to move a subset of the records, or what if you need to avoid service disruption that could occur during a move?
I’ll cover how we can export Secrets and then re-import into a newly created Key Vault using PowerShell, which makes the process as simple as possible, retaining as much of the original secret configuration as possible.
Script to Clone Secrets
Prerequisites
- Key Vault Secrets Reader on the source Key Vault
- Key Vault Secrets Officer on the target Key Vault
What the Script Does
The script below does a couple of key things –
- Connects to the Source Azure Tenant
- Retrieves all Secret details and values from the given Key Vault
- If cloning between Azure Tenants it will connect to the target tenant, if cloning between Subscriptions in the same Tenant it will switch Subscription and if neither is needed this step is skipped.
- It loops through each Secret, creating it in the target Key Vault as per below –
- Set up basic data to estimate completion time and to show progress
- Attempt to create the new Secret
- Set the Secret to disabled if required
- Record outcome of all steps for reporting purposes
- Once completed, a full report showing the total number of successful and failed clone operations is shown, along with a list of each failed operation and the reason for it.
The Script
# Enter your source details
$sourceTenantId = "#############"
$sourceSubscriptionId = "############"
$sourceKeyVaultName = "############"
# Enter your target details
$targetTenantId = "################"
$targetSubscriptionId = "#############"
$targetKeyVaultName = "################"
$ErrorActionPreference = "Stop"
# Connect to the source tenant
try {
Connect-AzAccount -Tenant $sourceTenantId -Subscription $sourceSubscriptionId
} catch {
Write-Error "Failed to connect to source tenant: $_"
exit 1
}
# Retrieve all Key Vault Secrets with their values as secure strings
try {
Write-Host "Retrieving all secrets from source Key Vault. This can take some time depending on the number of secrets"
$secrets = Get-AzKeyVaultSecret -VaultName $sourceKeyVaultName | ForEach-Object {
$_ | Select-Object *, @{Name='Value'; Expression={
ConvertTo-SecureString -String (Get-AzKeyVaultSecret -VaultName $sourceKeyVaultName -Name $_.Name -AsPlainText) -AsPlainText -Force
}}
}
} catch {
Write-Error "Failed to retrieve secrets from the source key vault: $_"
exit 1
}
# Connect to the target tenant if needed, otherwise just switch subscription
if($sourceTenantId -ne $targetTenantId) {
Write-Host "Connecting to target tenant with ID - $targetTenantId"
try {
Connect-AzAccount -Tenant $targetTenantId -Subscription $targetSubscriptionId
} catch {
Write-Error "Failed to connect to target tenant: $_"
exit 1
}
} elseif ($sourceSubscriptionId -ne $targetSubscriptionId) {
Write-Host "Switching active Subscription - $targetSubscriptionId"
try {
Set-AzContext -Subscription $targetSubscriptionId
} catch {
Write-Error "Failed to switch subscriptions: $_"
exit 1
}
}
# Create each secret in the target Key Vault
$outputResult = @()
$currentIndex = 1
$startTime = Get-Date
Write-Host "Starting to clone secrets"
foreach ($secret in $secrets) {
# Estimate time to completion and show progress
$estimation = ' '
$now = Get-Date
if ($currentIndex -gt 0) {
$elapsed = $now - $startTime # how much time has been spent
$average = $elapsed.TotalSeconds / $currentIndex # how many seconds per site
$totalSecondsToGo = ($secrets.Count - $currentIndex) * $average # seconds left
$span = New-TimeSpan -Seconds $totalSecondsToGo # time left
$estimatedCompletion = $now + $span # when it will be complete
$estimation = $estimatedCompletion.ToString() # readable estimation
}
Write-Progress -Id 0 -Activity "Cloning Secrets - Est. Completion - $($estimation)" -Status "$currentIndex of $($secrets.Count)" -PercentComplete (($currentIndex / $secrets.Count) * 100)
# Create the secret in the target Key Vault
try {
Set-AzKeyVaultSecret -VaultName $targetKeyVaultName `
-Name $secret.Name `
-Expires $secret.Expires `
-NotBefore $secret.NotBefore `
-ContentType $secret.ContentType `
-Tags $secret.Tags `
-SecretValue $secret.Value | Out-Null
} catch {
Write-Warning "Failed to create secret '$($secret.Name)': $_"
$outputResult += [PSCustomObject]@{
Name = $secret.Name
Status = "Failed to Create"
Reason = $_.Exception.Message
}
continue
}
# Disable the secret after creation if required, if we do it on creation then it doesn't populate the other values correctly
if($secret.Enabled -eq $false) {
try {
Set-AzKeyVaultSecret -VaultName $targetKeyVaultName `
-Name $secret.Name `
-Disable | Out-Null
} catch {
Write-Warning "Failed to disable secret '$($secret.Name)': $_"
$outputResult += [PSCustomObject]@{
Name = $secret.Name
Status = "Failed to Disable"
Reason = $_.Exception.Message
}
continue
}
}
$outputResult += [PSCustomObject]@{
Name = $secret.Name
Status = "Cloned Successfully"
Reason = $null
}
$currentIndex++
}
Write-Progress -Id 0 -Activity " " -Status " " -Completed
# Calculate total result counts and display
$successfullyClonedCount = ($outputResult | Where-object {$_.Status -eq "Cloned Successfully"}).Count
$failedCloneCount = ($outputResult | Where-object {$_.Status -eq "Failed to Create"}).Count
$failedDisableCount = ($outputResult | Where-object {$_.Status -eq "Failed to Disable"}).Count
Write-Host "=========================================================="
Write-Host "================== CLONE SECRETS RESULT =================="
if($successfullyClonedCount -gt 0) {
Write-Host -ForegroundColor Green "Successfully cloned $successfullyClonedCount Secrets"
}
if($failedCloneCount -gt 0) {
Write-Host -ForegroundColor Yellow "Failed to create $failedCloneCount Secrets"
}
if($failedDisableCount -gt 0) {
Write-Host -ForegroundColor Yellow "Failed to disable $failedDisableCount Secrets"
}
# If there are any failures then show them
if($failedCloneCount -gt 0 -or $failedDisableCount -gt 0) {
Write-Host ""
Write-Host "Secrets which failed to clone correctly are shown below"
$outputResult | Where-Object {$_.Status -ne "Cloned Successfully"} | Format-Table
}
Write-Host "Script Completed"







Leave a comment