Calculate "Billable Size" and Cost of Azure Blobs

Today my customer expressed a desire to utilize PowerShell to gather data regarding the size of their storage accounts. Not just that, but they wanted to understand the billable size of the containers / blobs / etc, and be able to calculate those costs.

A quick search on the web turned up a script (https://gallery.technet.microsoft.com/scriptcenter/Get-Billable-Size-of-32175802/view/Discussions) that had been developed back sometime in 2014. It still relied on pre-Resource Manager cmdlets and would not work in my test tenant. I'm no PowerShell guru by any means, but I was able to modify it to use the latest cmdlets and output accurate information regarding the containers within my subscription. I've uploaded the modified PowerShell script to gitHub. https://github.com/jimmielightner/GetBillableSize If you find a bug, want to improve the script, or just want to say hi - drop me a line or submit comments! 

Code is provided AS-IS with no warranty. If you're dumb enough to run it without looking at it, I can't be held liable if it breaks anything! :-)

<#
.SYNOPSIS
    Calculates cost of all blobs in a container or storage account.
.DESCRIPTION
    Enumerates all blobs in either one container or one storage account and sums
    up all costs associated.  This includes all block and page blobs, all metadata
    on either blobs or containers.  It also includes both committed and uncommitted
    blocks in the case that a blob is partially uploaded.
 
    The details of the calculations can be found in this post:
    http://blogs.msdn.com/b/windowsazurestorage/archive/2010/07/09/understanding-windows-azure-storage-billing-bandwidth-transactions-and-capacity.aspx
 
    Note: This script requires an Azure Storage Account to run.  The storage account 
    can be specified by setting the subscription configuration.  For example:
    Set-AzureSubscription -SubscriptionName "MySubscription" -CurrentStorageAccount "MyStorageAccount"
.EXAMPLE
    .\CalculateBlobCost.ps1 -StorageAccountName "mystorageaccountname"
    .\CalculateBlobCost.ps1 -StorageAccountName "mystorageaccountname" -ContainerName "mycontainername"
#>
 
param(
     # The name of the storage account to enumerate.
    [Parameter(Mandatory = $true)]
    [string]$StorageAccountName,
 
   # The name of the storage container to enumerate.
    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$ContainerName
)
 
# The script has been tested on Powershell 3.0
Set-StrictMode -Version 3
 
# Following modifies the Write-Verbose behavior to turn the messages on globally for this session
$VerbosePreference = "Continue"
 
# Check if Windows Azure Powershell is avaiable
if ((Get-Module -ListAvailable Azure) -eq $null)
{
    throw "Windows Azure Powershell not found! Please install from http://www.windowsazure.com/en-us/downloads/#cmd-line-tools"
}

<#
.SYNOPSIS
   Gets the size (in bytes) of a blob.
.DESCRIPTION
   Given a blob name, sum up all bytes consumed including the blob itself and any metadata,
   all committed blocks and uncommitted blocks.

   Formula reference for calculating size of blob:
       http://blogs.msdn.com/b/windowsazurestorage/archive/2010/07/09/understanding-windows-azure-storage-billing-bandwidth-transactions-and-capacity.aspx
.INPUTS
   $Blob - The blob to calculate the size of.
.OUTPUTS
   $blobSizeInBytes - The calculated sizeo of the blob.
#>
function Get-BlobBytes
{
    param (
        [Parameter(Mandatory=$true)]
        $Blob)
 
    # Base + blob name
    $blobSizeInBytes = 124 + $Blob.Name.Length * 2
 
    # Get size of metadata
    $metadataEnumerator = $Blob.ICloudBlob.Metadata.GetEnumerator()
    while ($metadataEnumerator.MoveNext())
    {
        $blobSizeInBytes += 3 + $metadataEnumerator.Current.Key.Length + $metadataEnumerator.Current.Value.Length
    }
 
    if ($Blob.BlobType -eq [Microsoft.WindowsAzure.Storage.Blob.BlobType]::BlockBlob)
    {
        $blobSizeInBytes += 8
        $Blob.ICloudBlob.DownloadBlockList() | 
            ForEach-Object { $blobSizeInBytes += $_.Length + $_.Name.Length }
    }
    else
    {
        $Blob.ICloudBlob.GetPageRanges() | 
            ForEach-Object { $blobSizeInBytes += 12 + $_.EndOffset - $_.StartOffset }
    }

    return $blobSizeInBytes
}
 
<#
.SYNOPSIS
   Gets the size (in bytes) of a blob container.
.DESCRIPTION
   Given a container name, sum up all bytes consumed including the container itself and any metadata,
   all blobs in the container together with metadata, all committed blocks and uncommitted blocks.
.INPUTS
   $Container - The container to calculate the size of. 
.OUTPUTS
   $containerSizeInBytes - The calculated size of the container.
#>
function Get-ContainerBytes
{
    param (
        [Parameter(Mandatory=$true)]
        [Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer]$Container)
 
    # Base + name of container
    $containerSizeInBytes = 48 + $Container.Name.Length * 2
 
    # Get size of metadata
    $metadataEnumerator = $Container.Metadata.GetEnumerator()
    while ($metadataEnumerator.MoveNext())
    {
        $containerSizeInBytes += 3 + $metadataEnumerator.Current.Key.Length + 
                                     $metadataEnumerator.Current.Value.Length
    }

    # Get size for Shared Access Policies
    $containerSizeInBytes += $Container.GetPermissions().SharedAccessPolicies.Count * 512
 
    # Calculate size of all blobs.
    $blobCount = 0
    Get-AzureStorageBlob -Context $storageContext -Container $Container.Name | 
        ForEach-Object { 
            $containerSizeInBytes += Get-BlobBytes $_ 
            $blobCount++
            }
 
    return @{ "containerSize" = $containerSizeInBytes; "blobCount" = $blobCount }
}

$storageAccount = Get-AzureRMStorageAccount -StorageAccountName $StorageAccountName -ErrorAction SilentlyContinue
if ($storageAccount -eq $null)
{
    throw "The storage account specified does not exist in this subscription."
}
 
# Instantiate a storage context for the storage account.
$storagePrimaryKey = (Get-AzureRMStorageAccountKey -Name $StorageAccount.StorageAccountName -ResourceGroupName $StorageAccount.ResourceGroupName).Value[0]
$storageContext = New-AzureStorageContext -StorageAccountName $StorageAccount.StorageAccountName -StorageAccountKey $storagePrimaryKey

# Get a list of containers to process.
$containers = New-Object System.Collections.ArrayList
if ($ContainerName.Length -ne 0)
{
    $container = Get-AzureStorageContainer -Context $storageContext `
                      -Name $ContainerName -ErrorAction SilentlyContinue | 
                          ForEach-Object { $containers.Add($_) } | Out-Null
}
else
{
    Get-AzureStorageContainer -Context $storageContext | ForEach-Object { $containers.Add($_) } | Out-Null
}

# Calculate size.
$sizeInBytes = 0
if ($containers.Count -gt 0)
{
    $containers | ForEach-Object { 
                      $result = Get-ContainerBytes $_.CloudBlobContainer                   
                      $sizeInBytes += $result.containerSize
                      Write-Verbose ("Container '{0}' with {1} blobs has a size of {2:F2}MB." -f `
                          $_.CloudBlobContainer.Name, $result.blobCount, ($result.containerSize / 1MB))
                      }
    Write-Output ("Total size calculated for {0} containers is {1:F2}GB." -f $containers.Count, ($sizeInBytes / 1GB))

    # Launch default browser to azure calculator for data management.
    Start-Process -FilePath http://www.windowsazure.com/en-us/pricing/calculator/?scenario=data-management
}
else
{
    Write-Warning "No containers found to process in storage account '$StorageAccountName'."
}

 

Another web blog? Ugh.

Yeah, yeah. I know. Since deleting my Facebook profile, I have a renewed need for an outlet to share my nerdy thoughts. Why not make use of that domain I've had registered for the last ten years? Anyway, you can expect to see random ramblings, posts about technology, and lots of pictures of cats! What the hell is the internet for, after all? :-)

Goodbye, Facebook!

I'm done. That's it. I call quits. I've deleted my Facebook account. Permanently.

According to my exported Facebook data, my initial registration date was way back on Sunday, October 24, 2004 at 12:17pm EDT. I was a member until Friday, November 11, 2016 at 9:35am EST. That is 4400 days, 21 hours, and 18 minutes. Quite a bit of time if you think about it. I'm afraid to even imagine how much of my life was wasted putzing around on that site.

It's interesting how cathartic dropping my membership has been. I've successfully shrugged off the leeches, the stalkers, and the people I just couldn't bring myself to unfriend. I don't have to see the political bullshit, the vague-booking, or any of the other drama. Now I don't have to make excuses or worry about hurting anyone's feelings: I don't have an account - and it's marvelous! Why didn't I do this sooner?

I do sometimes find myself trying to type facebook into the address bar of my browser if I let my brain go on autopilot. Funny how old habits die so damned hard. I won't lie, it's harder to keep in touch by text message or email (or even hand-written notes) but now it's more meaningful when I do. I like it.

I've had to find other things to fill my free time. It used to be so easy to "check in" on people and what they were doing. Without that crutch, it has been easier to focus on work and other more important things. I'm finally learning to code (which is fun and terrifying all at once) in C#. I actually sat down and played a video game, too. I feel much more productive than I ever have - and I actually have REAL free time to enjoy things.

So here's to enjoying life, enjoying friends (people who I actually interact with, not just online), and that thing called living.