Export And Normalize Azure Cost Data For Multi-Cloud Reporting: A Practical Azure FinOps Guide

The Multi-Cloud Cost Visibility Challenge

Organizations running workloads across Azure, AWS, and Google Cloud face a fundamental reporting problem: each provider uses different schemas, different terminology, and different pricing models. A column called “Cost” in Azure means something slightly different from “BlendedCost” in AWS or “cost” in GCP billing exports. Resource types, service categories, and billing periods all use provider-specific names and structures. Combining this data into a unified report without manual cleanup requires a normalization step that standardizes the schema before analysis begins.

The FinOps Open Cost and Usage Specification (FOCUS) was created to solve exactly this problem. It defines a standardized schema for cost and usage data across cloud providers, making it possible to combine Azure, AWS, and GCP billing data into a single dataset that can be queried, visualized, and reported on without per-provider transformations. Azure Cost Management supports FOCUS as a native export format, making it the natural starting point for multi-cloud cost reporting.

This guide covers how to export Azure cost data in FOCUS format, normalize it for multi-cloud consumption, combine it with data from other providers, and build reports that give leadership a single view across all cloud spending.

Understanding the FOCUS Specification

FOCUS stands for FinOps Open Cost and Usage Specification. It is governed by the FinOps Foundation (part of the Linux Foundation) and defines a common schema that cloud providers, SaaS vendors, and data center cost tools can adopt. Major providers including Microsoft Azure, AWS, Google Cloud, Oracle Cloud, and several others now produce FOCUS-compatible cost data.

Key FOCUS Columns

The specification standardizes column names and their semantics:

FOCUS Column Description Azure Equivalent
BilledCost Amount billed for the charge CostInBillingCurrency
EffectiveCost Amortized cost including commitment discounts CostInBillingCurrency (amortized view)
ChargePeriodStart Start of the charge period (UTC) Date
ChargePeriodEnd End of the charge period N/A (derived)
ServiceCategory High-level service classification (Compute, Storage, etc.) ServiceFamily
ServiceName Specific service name MeterCategory
ResourceId Unique identifier for the resource ResourceId (ARM path)
ResourceName Display name of the resource ResourceName
Provider Cloud provider name “Microsoft”
ChargeCategory Type of charge (Usage, Purchase, Tax, Credit) ChargeType
CommitmentDiscountId Reservation or savings plan identifier BenefitId
PricingUnit Unit that pricing is based on UnitOfMeasure

The power of FOCUS is in these standardized names. When your reporting queries use ServiceCategory and BilledCost, those queries work identically against Azure data, AWS data, and GCP data — no conditional logic, no provider-specific column mappings.

Exporting Azure Cost Data in FOCUS Format

Azure Cost Management supports FOCUS as a native export type called FocusCost. Configure it through the portal or REST API.

Portal Configuration

Navigate to Cost Management → Exports → Add. Select Cost and usage details (FOCUS) as the dataset type. Configure a daily schedule with Parquet format and Snappy compression for optimal storage and query performance. The FOCUS export combines both actual and amortized cost perspectives into a single dataset, eliminating the need for separate exports.

REST API Configuration

# Create FOCUS export for multi-cloud reporting
$scope = "/subscriptions/your-subscription-id"
$exportName = "focus-multicloud-daily"
$token = (Get-AzAccessToken -ResourceUrl https://management.azure.com).Token

$body = @{
    properties = @{
        format = "Parquet"
        compressionMode = "snappy"
        partitionData = $true
        dataOverwriteBehavior = "OverwritePreviousReport"
        definition = @{
            type = "FocusCost"
            timeframe = "MonthToDate"
            dataSet = @{ granularity = "Daily" }
        }
        deliveryInfo = @{
            destination = @{
                type = "AzureBlob"
                container = "multicloud-costs"
                resourceId = "/subscriptions/$subId/resourceGroups/rg-finops/providers/Microsoft.Storage/storageAccounts/stmulticloudcosts"
                rootFolderPath = "azure"
            }
        }
        schedule = @{
            status = "Active"
            recurrence = "Daily"
            recurrencePeriod = @{
                from = "2026-04-01T00:00:00Z"
                to = "2029-04-01T00:00:00Z"
            }
        }
    }
} | ConvertTo-Json -Depth 10

$headers = @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" }
$uri = "https://management.azure.com$scope/providers/Microsoft.CostManagement/exports/${exportName}?api-version=2025-03-01"
Invoke-RestMethod -Uri $uri -Method PUT -Headers $headers -Body $body

Note the storage path uses azure as the root folder, keeping Azure data separate from other providers’ exports in the same storage account.

Collecting Data from Other Cloud Providers

AWS Cost and Usage Report (CUR 2.0)

AWS supports FOCUS alignment through CUR 2.0 (Cost and Usage Report version 2). Configure a CUR export in the AWS Billing Console, selecting FOCUS-compatible columns. The export lands in an S3 bucket as Parquet files. To bring this data into your Azure-based reporting pipeline, set up a scheduled copy from the S3 bucket to your Azure Storage account using Azure Data Factory, or use a Logic App with the S3 connector.

Google Cloud Billing Export

GCP exports billing data to BigQuery, and FOCUS alignment is available through the standard billing export schema. Export the data from BigQuery to Google Cloud Storage in Parquet format, then transfer to your Azure Storage account. Google Cloud offers a Storage Transfer Service that can push data to Azure Blob Storage on a schedule.

Storage Organization

Organize the multi-cloud cost data in your storage account by provider:

multicloud-costs/
  ├── azure/
  │   └── focus-export/[YYYYMMDD-YYYYMMDD]/[RunID]/
  ├── aws/
  │   └── cur-focus/[YYYYMMDD-YYYYMMDD]/
  └── gcp/
      └── billing-focus/[YYYYMMDD-YYYYMMDD]/

Normalizing Non-FOCUS Data

While major providers support FOCUS, you may encounter data sources that do not: older AWS CUR v1 exports, SaaS billing data, or on-premises infrastructure costs. These require normalization to the FOCUS schema before they can be combined.

Azure Data Factory Transformation

Data Factory’s mapping data flows provide a visual interface for schema transformation. Create a pipeline that reads the source data, maps columns to FOCUS equivalents, standardizes date formats and currency codes, and writes the output in Parquet format to the same storage account.

Common transformations include:

  • Mapping AWS line_item_blended_cost to FOCUS BilledCost
  • Mapping AWS line_item_usage_start_date to FOCUS ChargePeriodStart
  • Standardizing service names (AWS “Amazon Elastic Compute Cloud” → “Compute” service category)
  • Adding a Provider column with the source cloud name

PowerShell Normalization Script

For simpler sources, a PowerShell script handles the transformation:

# Normalize AWS CUR v1 data to FOCUS schema
$awsData = Import-Csv "aws-cur-export.csv"

$normalized = $awsData | ForEach-Object {
    [PSCustomObject]@{
        Provider = "AWS"
        BilledCost = [double]$_.line_item_blended_cost
        EffectiveCost = [double]$_.line_item_unblended_cost
        ChargePeriodStart = [datetime]$_.line_item_usage_start_date
        ServiceCategory = switch -Wildcard ($_.product_product_family) {
            "Compute*" { "Compute" }
            "Storage*" { "Storage" }
            "Data Transfer*" { "Networking" }
            default { "Other" }
        }
        ServiceName = $_.line_item_product_code
        ResourceId = $_.line_item_resource_id
        ResourceName = ($_.line_item_resource_id -split '/')[-1]
        ChargeCategory = "Usage"
        Region = $_.product_region
        Currency = $_.line_item_currency_code
    }
}

$normalized | Export-Csv "aws-focus-normalized.csv" -NoTypeInformation

Building Cross-Cloud Reports

Once all provider data sits in FOCUS format within a shared store, reporting becomes straightforward because every query uses the same column names.

KQL: Total Spend by Provider

// Monthly spend comparison across cloud providers
MultiCloudCosts
| where ChargePeriodStart >= ago(180d)
| summarize MonthlyCost = sum(BilledCost) 
    by Provider, Month = startofmonth(ChargePeriodStart)
| order by Month asc, Provider
| render columnchart with (kind=stacked, title="Monthly Cloud Spend by Provider")

KQL: Service Category Comparison

// Compare compute, storage, and networking costs across providers
MultiCloudCosts
| where ChargePeriodStart >= startofmonth(ago(30d))
| where ServiceCategory in ("Compute", "Storage", "Networking")
| summarize TotalCost = sum(BilledCost) by Provider, ServiceCategory
| render barchart with (kind=stacked, title="Cost by Service Category per Provider")

These queries produce identical results regardless of which provider sourced the data, because FOCUS standardizes the column names and value semantics. A “Compute” charge from Azure and a “Compute” charge from AWS appear in the same service category without any join logic or mapping tables.

Handling Multi-Cloud Complexity

Currency Normalization

Different billing accounts may use different currencies. The FOCUS specification includes a BillingCurrency column that identifies the currency for each record. For consolidated reporting in a single currency, add a currency conversion step using daily exchange rates. Azure Data Factory or a simple lookup table in ADX can handle this transformation.

Service Name Harmonization

While FOCUS standardizes ServiceCategory at a high level (Compute, Storage, Networking, Databases), the ServiceName column retains provider-specific names. Azure reports “Virtual Machines” while AWS reports “Amazon Elastic Compute Cloud” for equivalent services. If your reports need provider-equivalent grouping at the service level, maintain a mapping table:

// Service mapping table approach
let ServiceMap = datatable(ServiceName:string, NormalizedService:string) [
    "Virtual Machines", "Compute - VMs",
    "Amazon Elastic Compute Cloud", "Compute - VMs",
    "Compute Engine", "Compute - VMs",
    "Azure SQL Database", "Database - Relational",
    "Amazon Relational Database Service", "Database - Relational",
    "Cloud SQL", "Database - Relational"
];
MultiCloudCosts
| lookup ServiceMap on ServiceName
| extend Service = coalesce(NormalizedService, ServiceName)
| summarize Cost = sum(BilledCost) by Service, Provider
| render barchart

Commitment Discount Normalization

Reserved instances in Azure, Savings Plans in AWS, and Committed Use Discounts in GCP all serve the same purpose but use different mechanics. The FOCUS CommitmentDiscountId and CommitmentDiscountType columns provide a common framework for analyzing commitment-based discounts across providers. Use these to build consolidated commitment utilization reports.

Multi-Cloud Reporting Architecture

A production-grade multi-cloud reporting architecture typically includes these components:

  1. Data collection — Scheduled exports from each provider landing in a centralized storage account, organized by provider
  2. Normalization — Data Factory pipelines that transform non-FOCUS data into FOCUS format
  3. Storage — Azure Data Lake Storage Gen2 as the unified cost data lake
  4. Analytics — Azure Data Explorer for KQL-based analysis, or Microsoft Fabric for integrated analytics and reporting
  5. Visualization — Power BI dashboards connected to the analytics layer, with provider comparison views

The Microsoft FinOps toolkit’s FinOps hub architecture supports this pattern natively. It accepts FOCUS exports from multiple providers and surfaces the combined data through pre-built Power BI reports that include provider comparison views.

Practical Recommendations

Start by enabling FOCUS exports in Azure since it requires no additional tooling — just select the FocusCost export type. Even before you add data from other providers, the FOCUS format gives you a future-proof schema that will not require migration when you go multi-cloud.

For the AWS side, switch to CUR 2.0 (which supports FOCUS) if you are still using CUR v1. The FOCUS-aligned export eliminates most of the normalization work. For GCP, use the BigQuery billing export and transform to FOCUS schema during the data transfer process.

Invest in a service mapping table early. The high-level ServiceCategory standardization in FOCUS covers most executive-level reporting needs, but engineering teams want provider-equivalent comparison at the service level. A maintained mapping table prevents ad-hoc and inconsistent translations across different reports.

Start simple with provider-level comparison (total cost by provider month-over-month) before drilling into cross-provider service comparison. The first report that shows all cloud spending in one chart changes the conversation from “how much do we spend on Azure” and “how much do we spend on AWS” asked separately to “how much do we spend on cloud” asked once, with the breakdown visible on the same page.

Multi-cloud cost reporting is not about choosing a winner between providers. It is about having accurate, normalized data that supports informed decisions about where to run workloads, how to negotiate rates, and how to allocate shared cloud investment across the business units that benefit from it. FOCUS makes that goal achievable without building a custom ETL pipeline for every new data source.

For more details, refer to the official documentation: What is Microsoft Cost Management.

Leave a Reply