Skip to content
Menu
Tech Automation Blog
  • About Author
  • Contact
Tech Automation Blog

Automate Azure billing report in Excel

Posted on March 6, 2020February 27, 2022

In my previous post I talked about generating AWS Billing data for last 12 months in Excel file and also showed you how to create a chart on Excel showing cost comparison all using Powershell. To me this is a nice and quick way to generate such report and email to stake holders automatically. I am sure, you also feel the same. No one wants to spend hours generating such reports 🙂

I will continue that trend and today will share a powershell script to generate similar report for Azure. Some of the pre-requisites remain same.Such as, you still need Microsoft Excel installed on the system from where you will run the script. However, unlike AWS, you need an Azure Account and password with permissions to all your subscriptions to access billing. The user account you will use must be an Organization account or native Azure AD Account. You can not use a Microsoft account.

Next, we need Azure AD Powershell module installed and imported. I will add those in the script. It will make sure it is installed and imported.

$AadModule = Get-Module -Name "AzureAD" -ListAvailable
if ([String]::IsNullOrEmpty($AadModule)){
    Install-Module -Name "AzureAD" -AllowClobber
}
$adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
[System.Reflection.Assembly]::LoadFrom($adal) | Select-Object FullName

Let’s define credentials as discussed above. For the sake of simplicity, I am hard coding user name and password in the script. This is not at all recommended for real environment. My suggestion is to leverage Key Vault to store secrets and retrieve those in script at run time.

$AutoAccName = "***************.onmicrosoft.com" # Use an Organizational Account or a native Azure AD user account. Don't use Microsoft account.
$AutoAccPwd = "****************"
$azpassword = ConvertTo-SecureString $AutoAccPwd  -AsPlainText -Force
$AzCred = New-Object System.Management.Automation.PSCredential ($AutoAccName, $azpassword)
Connect-AzAccount -Credential $AzCred

We will use Azure Cost Management REST API to retrieve billing data. As we are trying to get last 12 months data , we need to use custom time period in the query data for our POST api calls. Refer following Microsoft document to learn more about this API :

Azure Cost Management API

We will use a well known Microsoft Azure provided Client Id “1950a258-227b-4e31-a9cf-717495945fc2” for authentication along with supplied credentials. Refer to the full script to understand authentication process and how we are generating authorization bearer token before making API calls.

Rest of this script is quite straight forward. We will loop through each subscription( so, script will take care if you have multiple subscriptions with proper permission) and generate bill data for every month and store them in an array. Finally, we will use that data and generate Excel workbook with worksheet for each subscription and one final worksheet for summary chart.Quite easy!

Once you successfully run this script, you should see an Excel file popped up on your system as shown below :

12 months bill summary chart

Also, you should see an worksheet for each subscription if you have multiple :


Subscription based data

Here is the complete script. Download and save it as a .ps1 file. Run it and in few moments you have your data! Cool!

$oFileName = "AzureBillingDetailsForLast12Months.xlsx"
$oFile =  "${env:Temp}\$($oFileName)"
if(Test-Path $oFile){Remove-Item $oFile -Force -Confirm:$false}
New-Item -Path $oFile -ItemType File

$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"  #Well known Client id provided by Microsoft Azure to access Management APIs
$resourceAppIdURI = "https://management.core.windows.net/"
$costManagementApiVersion = "2019-10-01"


$Global:allSubsTotalCostArray = @()
$Global:allBillingPeriods = @()

#Install and initialize Azure AD Module
$AadModule = Get-Module -Name "AzureAD" -ListAvailable
if ([String]::IsNullOrEmpty($AadModule)){
    Install-Module -Name "AzureAD" -AllowClobber
}
$adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
[System.Reflection.Assembly]::LoadFrom($adal) | Select-Object FullName


#Retrieve Azure AD Service Account Credentials from Key Vault
$AutoAccName = "***************.onmicrosoft.com" # Use an Organizational Account or a native Azure AD user account. Don't use Microsoft account.
$AutoAccPwd = "**************"
$azpassword = ConvertTo-SecureString $AutoAccPwd  -AsPlainText -Force
$AzCred = New-Object System.Management.Automation.PSCredential ($AutoAccName, $azpassword)
Connect-AzAccount -Credential $AzCred
########

#Function to design Worksheet with some nice formatting
Function Decorate-Worksheet($wksht,$col,$tabCol){   
    For($c = 1; $c -le $col ; $c++){
        $wksht.Cells.Item(1,$c).Font.Size = 10
        $wksht.Cells.Item(1,$c).Font.Bold=$True
        $wksht.Cells.Item(1,$c).Font.Name = "Arial"
        $wksht.Cells.Item(1,$c).Interior.ColorIndex = 10
        $wksht.Cells.Item(1,$c).Font.ColorIndex = 2
        $wksht.Tab.ColorIndex = $tabCol;
    } 
    $dataRange = $wksht.UsedRange						
    $dataRange.EntireColumn.AutoFit() | Out-Null   
}
########

#Function to generate excel worksheet with Subscription based cost data
Function Add-ExcelWorksheetBySub($wb,$sub){
    $wksheet = $wb.Sheets.Add()
    $wksheet.Name =$sub
    $row = $col = 1
    $wksheet.Cells.Item($row,$col) = "Subscription Name"
    $col++
    $wksheet.Cells.Item($row,$col) = "Billing Month"
    $col++
    $wksheet.Cells.Item($row,$col) = "First Day"
    $col++
    $wksheet.Cells.Item($row,$col) = "Last Day"
    $col++
    $wksheet.Cells.Item($row,$col) = "Cost"
    $Global:allSubsBillingArray | ForEach-Object{    
        $row++
        $col = 1
        $wksheet.Cells.Item($row,$col) = $_.SubscriptionName
        $col++
        $wksheet.Cells.Item($row,$col) = $_.BilledMonth
        $col++
        $wksheet.Cells.Item($row,$col) = $_.FirstDay   
        $col++
        $wksheet.Cells.Item($row,$col) = $_.LastDay 
        $col++
        $wksheet.Cells.Item($row,$col) = $_.Cost 
    }
    Decorate-Worksheet $wksheet $col $tabColorIndex
    $tabColorIndex++
}
########

#Function to generate Chart Worksheet
Function Add-ChartExcelWorksheet($wb){
    $tmpWksheet = $workbook.WorkSheets.item('Sheet1') 
    $row = $col = 1
    $tmpWksheet.Cells.Item($row,$col) = "Billing Month"
    $col++
    $tmpWksheet.Cells.Item($row,$col) = "Total Cost"
    $row++ 
    $Global:allSubsTotalCostArray | Group-Object BilledMonth | ForEach-Object{
        $month = $_.Name
        $col = 1
        $tmpWksheet.Cells.Item($row,$col) = $month
        $col++
        $totalCost = 0
        $_.Group | Where-Object{$_.BilledMonth -eq $month} | ForEach-Object{
            $totalCost += $_.Cost
        }
        $tmpWksheet.Cells.Item($row,$col) = $totalCost
        $row++
    }
    $sheetData = $tmpWksheet.Range("A1").CurrentRegion
    $licWksheet = $workbook.sheets.Add()
    $licWksheet.Name = "Bill Summary For All Subs"
    $row = $col = 1
    $xlChart=[Microsoft.Office.Interop.Excel.XLChartType]
    $licChart = $licWksheet.Shapes.AddChart().Chart
    $licChart.ChartType = $xlChart::xlColumnClustered
    $licChart.SetSourceData($sheetData)
    $licChart.HasTitle = $true
    $licChart.ChartTitle.Text = "Last 12 Months Azure Bill For All Subscriptions Combined"
    $licWksheet.shapes.item("Chart 1").top = 50
    $licWksheet.shapes.item("Chart 1").left = 250
    $licWksheet.Tab.Color = 10;
    $tmpWksheet.Visible=$False
}
########

#Function to generate Azure billing data
Function Generate-AzureBillByMonth(){
    #Parse through each subscription
    Get-AzSubscription | ForEach-Object {    
        $Global:allSubsBillingArray = @()       
        $tenantId = $_.TenantId        
        $authority = "https://login.windows.net/$tenantId"
        $userCredentials = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential -ArgumentList $AutoAccName, $AutoAccPwd
        $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
        $authResult = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($authContext, $resourceAppIdURI, $clientid, $userCredentials);
        $authHeader = @{ 'Authorization' = "Bearer " + $authResult.Result.AccessToken }
    
        $subscription = $_.Name
        $subscriptionId = $_.Id
        $costManagementUri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.CostManagement/query?api-version=$($costManagementApiVersion)"     
        ForEach($billingPeriod in $Global:allBillingPeriods){
            Start-Sleep -Seconds 1
            $billedAmount = 0.00
            $billedMonth = $billingPeriod.Name         
            $costData = ""
            $startDate = Get-Date ($billingPeriod.FirstDay) -UFormat "%Y-%m-%dT%H:%M:%S.000Z"
            $endDate = Get-Date ($billingPeriod.LastDay) -UFormat "%Y-%m-%dT%H:%M:%S.000Z"
            $querybody="{
                `"type`": `"Usage`",
                `"timeframe`": `"Custom`",
                `"timePeriod`": {
                    `"from`": `"$startDate`",
                    `"to`": `"$endDate`"
                },
                `"dataset`": {
                `"granularity`": `"Accumulated`",
                `"aggregation`": {
                    `"totalCost`": {
                    `"name`": `"PreTaxCost`",
                    `"function`": `"Sum`"
                    }
                }
                }
            }"
    
            $costData = Invoke-RestMethod -Uri $costManagementUri -Method Post -Headers $authHeader -Body $querybody -ContentType "Application/JSON"
            if(!([String]::IsNullOrEmpty($costData.properties.rows))){
                $billedAmount = $costData.properties.rows[0][0]                
            }
            else{
                $billedAmount = 0.00                
            }
            $objCost = New-Object psobject
            $objCost | Add-Member SubscriptionName $subscription
            $objCost | Add-Member Cost ([math]::Round($billedAmount,2))
            $objCost | Add-Member BilledMonth $billedMonth
            $objCost | Add-Member FirstDay $billingPeriod.FirstDay
            $objCost | Add-Member LastDay $billingPeriod.LastDay
            $Global:allSubsBillingArray += $objCost

            $objTotalCost = New-Object psobject
            $objTotalCost | Add-Member SubscriptionName $subscription
            $objTotalCost | Add-Member Cost ([math]::Round($billedAmount,2))
            $objTotalCost | Add-Member BilledMonth $billedMonth
            $Global:allSubsTotalCostArray += $objTotalCost

        }
        Add-ExcelWorksheetBySub $workbook $subscription
    }
    Add-ChartExcelWorkSheet $workbook
}
########

# Generate an Object array of last 12 billing months and reverse the order
1..12 | ForEach-Object{
    $billMonth = "$((Get-Culture).DateTimeFormat.GetAbbreviatedMonthName((Get-Date).AddMonths(-($_)).Month))-$((Get-Date).AddMonths(-($_)).Year)"
    $firstDay = "$((Get-Date).AddMonths(-($_)).Year)-$(("{0:d2}" -f ((Get-Date).AddMonths(-($_)).Month)))-01"
    $lastDay = "$((Get-Date).AddMonths(-$_).Year)-$(("{0:d2}" -f ((Get-Date).AddMonths(-$_).Month)))-$([DateTime]::DaysInMonth((get-date).AddMonths(-$_).Year, (get-date).AddMonths(-$_).Month))"
    
    $objPeriod = New-Object psObject
    $objPeriod | Add-Member NoteProperty Name $billMonth
    $objPeriod | Add-Member NoteProperty FirstDay $firstDay
    $objPeriod | Add-Member NoteProperty LastDay $lastDay
    $Global:allBillingPeriods += $objPeriod
}
[array]::Reverse($Global:allBillingPeriods)

#Create new Excel file and initiate workbook
$excel = New-Object -ComObject excel.application
$excel.visible = $False
$workbook = $excel.Workbooks.Add()
$tabColorIndex = 30
########

#Call function to generate azure billing data
Generate-AzureBillByMonth
########

#Stop alert window to overwrite excel file
$excel.DisplayAlerts = $false
#Save excel file
$workbook.SaveAs($oFile,51)
$workbook.Saved = $true
$workbook.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
#Open Excel file on Windows Desktop
Start-Process $oFile

Post your comment if this script works for you or you face any issue! See you in next blog!

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on LinkedIn (Opens in new window) LinkedIn
June 2025
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
30  
« May    

Recent Posts

  • Monitor and alert Azure Service Health issues May 5, 2020
  • AWS IAM User access review May 3, 2020
  • Integrate Azure Security Center with Event Hub April 28, 2020
  • Add Tags to Azure Subscription April 24, 2020
  • Automate Azure billing report in Excel March 6, 2020

Categories

©2025 Tech Automation Blog | Powered by SuperbThemes