Skip to main content

Azure KeyVault Secrets Migration Commands

🧱 Migration Inputs

Azure Key Vault Secrets Migration

1. Verify Azure CLI Installation

Run:

az --version

If you see a message like:

You have 2 update(s) available. Consider updating your CLI installation with 'az upgrade'

Then run:

az upgrade

2. Select the Correct Azure Subscription

List subscriptions:

az account list -o table

Set your subscription:

az account set --subscription "a4042c0e-f555-440f-8a16-6e0170d10c98"

3. Confirm Source and Destination Vault Names

This page uses an effective environment name:

  • If env is local or dev, the effective env is dev
  • For all other environments, the effective env is {env}

Source Vault:

dev-ts-keyvault

Destination Vault:

dev-se-keyvault

4. Verify the Destination Vault Exists

Run:

az keyvault show -g dev-se-resource-group -n dev-se-keyvault -o table

You should see the Key Vault details in the output.

5. Inspect a Known Secret in the Source Vault

If you already know an existing secret name (example: dev-aws-admin-log-ui-app-client-id), fetch its value from the source vault:

az keyvault secret show --vault-name dev-ts-keyvault --name "dev-aws-admin-log-ui-app-client-id" --query "value" -o tsv

You should get a secret value (example: xxxxxxxx).

6. List All Secret Names (Source Vault)

List secret names in the source vault:

az keyvault secret list --vault-name dev-ts-keyvault --query "[].name" -o tsv

This should print a list of secret names.

7. Copy Secrets from Source Vault to Destination Vault (PowerShell)

Run this PowerShell script to copy all secrets (including attributes and tags):

$SrcVault = "dev-ts-keyvault"
$DstVault = "dev-se-keyvault"

# Get all secret names from source
$names = az keyvault secret list --vault-name $SrcVault --query "[].name" -o tsv

if (-not $names) {
Write-Host "No secrets found in source vault."
return
}

foreach ($name in $names) {
Write-Host "Copying: $name"

$json = az keyvault secret show --vault-name $SrcVault --name $name -o json | ConvertFrom-Json

if (-not $json.value) {
  Write-Host "  Skipping $name (no value returned)"
  continue
}

$value       = $json.value
$contentType = $json.contentType
$enabled     = $json.attributes.enabled
$notBefore   = $json.attributes.notBefore
$expires     = $json.attributes.expires
$tags        = $json.tags

# Build argument list
$args = @(
  "keyvault","secret","set",
  "--vault-name", $DstVault,
  "--name", $name,
  "--value", $value
)

if ($contentType) { $args += @("--content-type", $contentType) }
if ($notBefore)   { $args += @("--not-before",  $notBefore) }
if ($expires)     { $args += @("--expires",     $expires) }
if ($enabled -eq $false) { $args += @("--disabled") }

# Add tags if present: --tags k=v k2=v2
if ($tags) {
  $args += "--tags"
  foreach ($p in $tags.PSObject.Properties) {
    $args += "$($p.Name)=$($p.Value)"
  }
}

# Execute as: az <args...>
& az @args | Out-Null
}

Write-Host "Done. Secrets copied from $SrcVault to $DstVault."

After the script completes, all secrets should be copied from the source vault to the destination vault.

8. Validate the Secret Counts Match

Count secrets in the source vault:

az keyvault secret list --vault-name dev-ts-keyvault -o tsv | Measure-Object -Line

Count secrets in the destination vault:

az keyvault secret list --vault-name dev-se-keyvault -o tsv | Measure-Object -Line

The output should show the same line count for both vaults.

Rename Secrets from Source Prefix to Destination Prefix

This section renames secrets inside the destination vault:

  • local-ts-local-se-
  • dev-ts-dev-se-

9. Step 1 — Configure Variables

Set variables (first run should be dry-run):

$VaultName = "dev-se-keyvault"

# Prefix mappings (old → new)
$PrefixMap = @{
"local-ts-" = "local-se-"
"dev-ts-"   = "dev-se-"
}

# Safety switches
$DryRun = $true        # ← SET TO $false to actually rename
$DeleteOld = $false    # ← SET TO $true only AFTER verification

Important (first run):

$DryRun = $true
$DeleteOld = $false

10. Step 2 — Dry Run Rename Script

Run this script:

Write-Host "Vault: $VaultName"
Write-Host "DryRun: $DryRun"
Write-Host "DeleteOld: $DeleteOld"
Write-Host "---------------------------"

# Get all secret names
$secretNames = az keyvault secret list `
--vault-name $VaultName `
--query "[].name" -o tsv

foreach ($oldName in $secretNames) {

foreach ($oldPrefix in $PrefixMap.Keys) {

  if ($oldName.StartsWith($oldPrefix)) {

    $newPrefix = $PrefixMap[$oldPrefix]
    $newName = $oldName -replace "^$oldPrefix", $newPrefix

    Write-Host "Renaming:"
    Write-Host "  OLD → $oldName"
    Write-Host "  NEW → $newName"

    # Fetch source secret
    $json = az keyvault secret show `
      --vault-name $VaultName `
      --name $oldName -o json | ConvertFrom-Json

    if (-not $json.value) {
      Write-Host "  ⚠ Skipped (no value)"
      continue
    }

    # Build args
    $args = @(
      "keyvault","secret","set",
      "--vault-name", $VaultName,
      "--name", $newName,
      "--value", $json.value
    )

    if ($json.contentType) {
      $args += @("--content-type", $json.contentType)
    }

    if ($json.attributes.notBefore) {
      $args += @("--not-before", $json.attributes.notBefore)
    }

    if ($json.attributes.expires) {
      $args += @("--expires", $json.attributes.expires)
    }

    if ($json.attributes.enabled -eq $false) {
      $args += "--disabled"
    }

    if ($json.tags) {
      $args += "--tags"
      foreach ($p in $json.tags.PSObject.Properties) {
        $args += "$($p.Name)=$($p.Value)"
      }
    }

    if ($DryRun) {
      Write-Host "  🟡 DRY-RUN: would create $newName"
    }
    else {
      & az @args | Out-Null
      Write-Host "  ✅ Created $newName"

      if ($DeleteOld) {
        az keyvault secret delete `
          --vault-name $VaultName `
          --name $oldName -o none
        Write-Host "  🗑 Deleted $oldName"
      }
    }

    Write-Host ""
  }
}
}

Write-Host "Done."

You should see output like:

Renaming:
OLD  dev-ts-conf-db-database-name
NEW  dev-se-conf-db-database-name
DRY-RUN: would create dev-se-conf-db-database-name

11. Step 3 — Set DryRun to False (Create New Names)

Update variables:

$DryRun = $false
$DeleteOld = $false

Run the Step 2 script again.

12. Step 4 — Delete Old Names (After Verification)

After verifying your services are reading the new secret names, update variables:

$DryRun = $false
$DeleteOld = $true

Run the Step 2 script again to delete old secret names.