Microsoft Azureintermediate

How to Change the Storage Account or Export Configuration for Azure Billing Data

Update or recreate cost export settings with new configurations

12 min readUpdated January 2025

How to Change the Storage Account or Export Configuration for Azure Billing Data

Azure Cost Management exports deliver billing data to Azure Storage on a scheduled basis. As your organization's needs evolve, you may need to change the destination storage account, modify export settings, or reconfigure data retention policies. This guide explains how to safely update or recreate cost export configurations without losing historical data or disrupting analytics pipelines.

Common scenarios include migrating to a new storage account for better organization, changing export frequency, adding or removing cost columns, or consolidating exports from multiple subscriptions into a centralized FinOps storage account.

Prerequisites

Before you begin, ensure you have:

  • Cost Management Contributor or Billing Account Contributor role
  • Access to the Azure portal (portal.azure.com)
  • Storage Blob Data Contributor role on both the current and new storage accounts
  • Knowledge of existing export configurations
  • PowerShell 7.0+ or Azure CLI installed (for automation)
  • Backup plan for existing export data

Understanding Export Configuration Options

Azure Cost Management exports support various configuration options:

Export Scope

  • Subscription: Single subscription costs
  • Resource Group: Specific resource group costs
  • Billing Account: All subscriptions under a billing account
  • Management Group: Aggregated costs across multiple subscriptions

Export Types

  • Actual Cost: Pay-as-you-go pricing
  • Amortized Cost: Reservation and savings plan costs spread over time
  • Usage: Detailed usage metrics without pricing

Export Frequency

  • Daily: New file created each day with previous day's costs
  • Monthly: Single file generated monthly
  • One-time: Ad-hoc export for specific date range

Step-by-Step Guide

Method 1: Modify Existing Export via Azure Portal

Step 1: Navigate to Current Export

  1. Sign in to the Azure portal
  2. Navigate to Cost Management + Billing
  3. Select your billing scope (subscription, billing account, etc.)
  4. Click Exports in the left menu under Cost Management
  5. Locate the export you want to modify

Step 2: Review Current Configuration

  1. Click on the export name to view details
  2. Note the current settings:
    • Storage account name and container
    • Export frequency and schedule
    • Data type (Actual, Amortized, Usage)
    • Date range
    • File format

Step 3: Modify Export Settings

Unfortunately, Azure doesn't allow direct modification of storage account destination. You must delete and recreate the export. However, you can modify:

  • Schedule: Click Edit next to Schedule
  • Date range: Modify the time period
  • Status: Enable or disable the export

For storage account changes, proceed to Method 2.

Method 2: Recreate Export with New Storage Account (Portal)

Step 1: Prepare New Storage Account

First, ensure the new storage account exists and is properly configured:

# Create new storage account if needed
$resourceGroupName = "finops-rg"
$storageAccountName = "newcostmgmtstorage"
$location = "eastus"

New-AzStorageAccount `
  -ResourceGroupName $resourceGroupName `
  -Name $storageAccountName `
  -Location $location `
  -SkuName Standard_LRS `
  -Kind StorageV2 `
  -AccessTier Hot `
  -AllowBlobPublicAccess $false

# Create container for exports
$ctx = (Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName).Context
New-AzStorageContainer -Name "cost-exports" -Context $ctx -Permission Off

Step 2: Document Current Export Configuration

Before deleting the old export, save its configuration:

# Export current configuration to JSON
$scope = "/subscriptions/12345678-1234-1234-1234-123456789012"
$exportName = "daily-cost-export"

$currentExport = Get-AzCostManagementExport -Scope $scope -Name $exportName
$currentExport | ConvertTo-Json -Depth 10 | Out-File "export-backup-$(Get-Date -Format 'yyyyMMdd').json"

Step 3: Copy Historical Data to New Storage Account

Before deleting the old export, copy existing data:

# Use AzCopy to transfer data
azcopy login

# Copy all existing export data
azcopy copy \
  "https://oldstorageaccount.blob.core.windows.net/cost-exports/*" \
  "https://newcostmgmtstorage.blob.core.windows.net/cost-exports/" \
  --recursive=true

Or using PowerShell:

# Get source and destination contexts
$sourceAccount = "oldstorageaccount"
$destAccount = "newcostmgmtstorage"
$containerName = "cost-exports"

$sourceCtx = New-AzStorageContext -StorageAccountName $sourceAccount -UseConnectedAccount
$destCtx = New-AzStorageContext -StorageAccountName $destAccount -UseConnectedAccount

# Copy all blobs
$blobs = Get-AzStorageBlob -Container $containerName -Context $sourceCtx

foreach ($blob in $blobs) {
    Copy-AzStorageBlob `
      -SrcContainer $containerName `
      -SrcBlob $blob.Name `
      -DestContainer $containerName `
      -DestBlob $blob.Name `
      -SrcContext $sourceCtx `
      -DestContext $destCtx `
      -Force

    Write-Host "Copied: $($blob.Name)"
}

Step 4: Create New Export with Updated Configuration

In the Azure Portal:

  1. Navigate to Cost Management + Billing > Exports
  2. Click + Add or + Create
  3. Configure the new export:
    • Name: Use the same name or a new descriptive name
    • Export type: Select Actual Cost, Amortized Cost, or Usage
    • Frequency: Daily, Monthly, or One-time
    • Time period: Month-to-date, Last month, or Custom
  4. Under Storage account details:
    • Subscription: Select the subscription containing the new storage account
    • Storage account: Select the new storage account
    • Container: Select the container for exports
    • Directory: (Optional) Specify a folder path
  5. Click Create

Step 5: Verify New Export

  1. Wait for the first export to complete (up to 24 hours for daily exports)
  2. Navigate to the new storage account
  3. Browse to the container and verify files are being created
  4. Compare file structure with backed-up configuration

Step 6: Delete Old Export (Optional)

Once the new export is working correctly:

  1. Navigate to the old export
  2. Click Delete
  3. Confirm deletion
  4. Keep the old storage account until you've verified all data integrity

Method 3: Change Export Configuration Using PowerShell

# Connect to Azure
Connect-AzAccount

# Define scope and export details
$scope = "/subscriptions/12345678-1234-1234-1234-123456789012"
$oldExportName = "daily-cost-export"
$newExportName = "daily-cost-export-v2"

# Get current export configuration
$currentExport = Get-AzCostManagementExport -Scope $scope -Name $oldExportName

# Define new storage account
$newStorageAccountId = "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/finops-rg/providers/Microsoft.Storage/storageAccounts/newcostmgmtstorage"
$containerName = "cost-exports"
$directory = "subscription-exports"

# Create new export with updated configuration
$newExportParams = @{
    Scope = $scope
    Name = $newExportName
    ScheduleStatus = "Active"
    ScheduleRecurrence = "Daily"
    RecurrencePeriodFrom = (Get-Date).ToString("yyyy-MM-ddT00:00:00Z")
    RecurrencePeriodTo = (Get-Date).AddYears(5).ToString("yyyy-MM-ddT00:00:00Z")
    Format = "Csv"
    DefinitionType = "ActualCost"
    DefinitionTimeframe = "MonthToDate"
    DestinationResourceId = $newStorageAccountId
    DestinationContainer = $containerName
    DestinationRootFolderPath = $directory
}

# Create the new export
New-AzCostManagementExport @newExportParams

Write-Host "New export '$newExportName' created successfully"

# Optionally delete the old export
# Remove-AzCostManagementExport -Scope $scope -Name $oldExportName

Method 4: Update Multiple Exports Using Azure CLI Script

For organizations with many exports, automate the migration:

#!/bin/bash

# Configuration
OLD_STORAGE_ACCOUNT="oldstorageaccount"
NEW_STORAGE_ACCOUNT="newcostmgmtstorage"
RESOURCE_GROUP="finops-rg"
SUBSCRIPTION_ID="12345678-1234-1234-1234-123456789012"

# Get new storage account resource ID
NEW_STORAGE_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.Storage/storageAccounts/${NEW_STORAGE_ACCOUNT}"

# List all exports for the subscription
SCOPE="/subscriptions/${SUBSCRIPTION_ID}"

echo "Listing all exports for scope: ${SCOPE}"
EXPORTS=$(az costmanagement export list --scope "${SCOPE}" --query "[].name" -o tsv)

for EXPORT_NAME in $EXPORTS; do
    echo "Processing export: ${EXPORT_NAME}"

    # Get export details
    EXPORT_JSON=$(az costmanagement export show --name "${EXPORT_NAME}" --scope "${SCOPE}")

    # Extract configuration
    EXPORT_TYPE=$(echo $EXPORT_JSON | jq -r '.definition.type')
    RECURRENCE=$(echo $EXPORT_JSON | jq -r '.schedule.recurrence')
    CONTAINER=$(echo $EXPORT_JSON | jq -r '.deliveryInfo.destination.container')
    DIRECTORY=$(echo $EXPORT_JSON | jq -r '.deliveryInfo.destination.rootFolderPath')

    # Create new export name
    NEW_EXPORT_NAME="${EXPORT_NAME}-migrated"

    echo "Creating new export: ${NEW_EXPORT_NAME}"

    # Create new export
    az costmanagement export create \
      --name "${NEW_EXPORT_NAME}" \
      --scope "${SCOPE}" \
      --storage-account-id "${NEW_STORAGE_ID}" \
      --storage-container "${CONTAINER}" \
      --storage-directory "${DIRECTORY}" \
      --timeframe "MonthToDate" \
      --type "${EXPORT_TYPE}" \
      --recurrence "${RECURRENCE}"

    echo "Completed: ${NEW_EXPORT_NAME}"
done

echo "Migration complete!"

Method 5: Change Export Column Configuration

To add or remove columns from exports, recreate the export with specific column selections:

# Define specific columns to include
$columns = @(
    "Date",
    "ResourceId",
    "ResourceLocation",
    "ResourceGroupName",
    "ResourceType",
    "MeterCategory",
    "MeterSubcategory",
    "Meter",
    "Cost",
    "UnitPrice",
    "Quantity",
    "Tags"
)

# Create export with custom columns
$exportParams = @{
    Scope = $scope
    Name = "custom-column-export"
    ScheduleStatus = "Active"
    ScheduleRecurrence = "Daily"
    RecurrencePeriodFrom = (Get-Date).ToString("yyyy-MM-ddT00:00:00Z")
    RecurrencePeriodTo = (Get-Date).AddYears(1).ToString("yyyy-MM-ddT00:00:00Z")
    Format = "Csv"
    DefinitionType = "Usage"
    DefinitionTimeframe = "MonthToDate"
    DatasetColumn = $columns
    DestinationResourceId = $storageAccountId
    DestinationContainer = "cost-exports"
    DestinationRootFolderPath = "custom-exports"
}

New-AzCostManagementExport @exportParams

Best Practices

1. Test Before Migrating Production Exports

Create a test export to the new storage account first:

# Create test export
$testExportParams = @{
    Scope = $scope
    Name = "test-export-migration"
    ScheduleStatus = "Active"
    ScheduleRecurrence = "Daily"
    RecurrencePeriodFrom = (Get-Date).ToString("yyyy-MM-ddT00:00:00Z")
    RecurrencePeriodTo = (Get-Date).AddDays(7).ToString("yyyy-MM-ddT00:00:00Z")
    Format = "Csv"
    DefinitionType = "ActualCost"
    DefinitionTimeframe = "MonthToDate"
    DestinationResourceId = $newStorageAccountId
    DestinationContainer = "cost-exports-test"
    DestinationRootFolderPath = "test"
}

New-AzCostManagementExport @testExportParams

2. Implement Export Versioning

Use directory paths to track export configuration changes:

cost-exports/
├── v1/
│   └── 2024-01/
│       └── export-data.csv
├── v2/
│   └── 2024-02/
│       └── export-data.csv
└── current/  (symlink or latest version)

3. Maintain Change Log

Document all export configuration changes:

# Create change log
$changeLog = @{
    Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    ExportName = $exportName
    ChangeType = "Storage Account Migration"
    OldStorageAccount = $oldStorageAccount
    NewStorageAccount = $newStorageAccount
    ModifiedBy = $env:USERNAME
    Reason = "Consolidating to centralized FinOps storage"
}

$changeLog | ConvertTo-Json | Add-Content "export-change-log.json"

4. Configure Lifecycle Management on New Storage Account

Set up automatic archival for old exports:

# Create lifecycle management rule
$rule = New-AzStorageAccountManagementPolicyRule `
  -Name "archive-old-exports" `
  -Blob `
  -TierToCool -DaysAfterModificationGreaterThan 90 `
  -TierToArchive -DaysAfterModificationGreaterThan 180 `
  -Delete -DaysAfterModificationGreaterThan 365

$policy = Set-AzStorageAccountManagementPolicy `
  -ResourceGroupName $resourceGroupName `
  -StorageAccountName $newStorageAccount `
  -Rule $rule

5. Set Up Alerts for Export Failures

Create alerts to monitor export health:

# Create action group for notifications
az monitor action-group create \
  --name "cost-export-alerts" \
  --resource-group $RESOURCE_GROUP \
  --short-name "CostAlert" \
  --email "[email protected]"

# Create alert rule (requires Log Analytics workspace)
az monitor metrics alert create \
  --name "cost-export-failure-alert" \
  --resource-group $RESOURCE_GROUP \
  --scopes $NEW_STORAGE_ID \
  --condition "total BlobCount < 1" \
  --window-size 24h \
  --evaluation-frequency 1h \
  --action cost-export-alerts

6. Validate Data Integrity After Migration

Compare row counts and cost totals:

# Compare old vs new export data
$oldData = Import-Csv "old-export.csv"
$newData = Import-Csv "new-export.csv"

$comparison = [PSCustomObject]@{
    OldRowCount = $oldData.Count
    NewRowCount = $newData.Count
    OldTotalCost = ($oldData | Measure-Object -Property Cost -Sum).Sum
    NewTotalCost = ($newData | Measure-Object -Property Cost -Sum).Sum
    RowCountDiff = $newData.Count - $oldData.Count
    CostDiff = ($newData | Measure-Object -Property Cost -Sum).Sum - ($oldData | Measure-Object -Property Cost -Sum).Sum
}

$comparison | Format-Table

Troubleshooting

Issue: "Permission denied" when creating export to new storage account

Cause: Missing IAM permissions on the new storage account.

Solution:

# Grant Cost Management service principal access to storage account
$storageAccountId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName"

# Get Cost Management service principal (varies by region)
# For global: f7f7e7e7-e7e7-e7e7-e7e7-e7e7e7e7e7e7

New-AzRoleAssignment `
  -ObjectId "f7f7e7e7-e7e7-e7e7-e7e7-e7e7e7e7e7e7" `
  -RoleDefinitionName "Storage Blob Data Contributor" `
  -Scope $storageAccountId

Issue: Historical data missing after migration

Cause: Export data wasn't copied before deleting old export.

Solution:

  1. If old storage account still exists, copy data immediately
  2. If deleted, check for backup or soft-delete:
    # Check for soft-deleted storage accounts
    Get-AzResource -ResourceType "Microsoft.Storage/deletedAccounts" |
      Where-Object { $_.Name -eq $oldStorageAccount }
    
    # Restore if found (within 90-day retention)
    Restore-AzStorageAccount -Name $oldStorageAccount -ResourceGroupName $resourceGroupName
    

Issue: New export has different file structure

Cause: Export type or column configuration changed.

Solution:

  1. Verify export configuration matches original:

    Compare-Object `
      (Get-Content "export-backup.json" | ConvertFrom-Json | Select-Object -ExpandProperty Definition) `
      (Get-AzCostManagementExport -Scope $scope -Name $newExportName | Select-Object -ExpandProperty Definition)
    
  2. Recreate export with exact configuration from backup

Issue: Downstream tools fail after storage account change

Cause: Hard-coded storage account references in pipelines.

Solution:

  1. Update connection strings in:

    • Azure Synapse linked services
    • Power BI data sources
    • Azure Data Factory pipelines
    • Custom scripts and automation
  2. Use environment variables or Azure Key Vault for storage account references:

    # Store connection string in Key Vault
    $connectionString = (Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $newStorageAccount |
      Get-AzStorageAccountKey | Select-Object -First 1).Value
    
    Set-AzKeyVaultSecret `
      -VaultName "finops-keyvault" `
      -Name "CostExportStorageConnectionString" `
      -SecretValue (ConvertTo-SecureString $connectionString -AsPlainText -Force)
    

Issue: Export running but no data appearing in new storage account

Cause: Export schedule hasn't triggered yet or permissions issue.

Solution:

  1. Manually trigger the export:

    az costmanagement export run --name $EXPORT_NAME --scope $SCOPE
    
  2. Check export run history:

    Get-AzCostManagementExport -Scope $scope -Name $exportName |
      Select-Object -ExpandProperty RunHistory
    
  3. Verify storage account firewall settings allow Cost Management service

Next Steps

After successfully changing your export configuration, consider these related tasks:

  1. Secure the new storage account: How to Secure Cost Management Data in Azure Storage and Synapse
  2. Monitor export status: How to Monitor Cost Export Status and Data Freshness in Azure
  3. Configure IAM roles: How to Grant IAM Roles for Azure Cost Management Data Export
  4. Update analytics pipelines: How to View Azure Billing Data in Azure Synapse or Power BI

Related Resources

Frequently Asked Questions

Find answers to common questions

To preserve historical billing data during a storage account change, first copy the existing export data from the old storage account to the new one using AzCopy or PowerShell. This ensures that no data is lost before you delete the old export. Additionally, document the current export configuration by exporting it to JSON before making changes. After verifying that the new export is functioning correctly, you can delete the old export if desired. Always maintain a backup until you confirm data integrity in the new setup.

Need Professional Help?

Our team of experts can help you implement and configure these solutions for your organization.