public/New-SilkTCOCostArray.ps1

<#
    .SYNOPSIS
    Generates an Azure Resource Cost report.

    .EXAMPLE
    generateResourceCostArray -ResourceGroupName MyResourceGroup

    This generates the cost reports for the RG MyResourceGroup and loads them into a simple date-stamped CSV report.

    .EXAMPLE
    generateResourceCostArray -days 28

    This generates the cost reports for the current azure subscription context and loads them into a simple date-stamped CSV report for the last 28 days.


#>


function New-SilkTCOCostArray {
    param(
        [Parameter()]
        [array] $ResourceGroupName,
        [Parameter()]
        [int] $days = 1,
        [Parameter()]
        [int] $offsetDays = 1
    )

    $requiredModules = @('Az.CostManagement', 'Az.Resources')
    foreach ($module in $requiredModules) {
        if (-not (Get-Module -ListAvailable -Name $module)) {
            Write-Warning "$module module not found. Installing..."
            Install-Module -Name $module -Force -AllowClobber
        }
        Import-Module $module
    }

    $azcontext = get-azcontext

    if ($ResourceGroupName) {
        $rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
        if (!$rg) {
            Write-Error "Resource group '$ResourceGroupName' not found."
            exit
        }
        $scope = "/subscriptions/$($azcontext.subscription.id)/resourceGroups/$ResourceGroupName"
    } else {
        $scope = "/subscriptions/$($azcontext.subscription.id)"
    }

    Write-Verbose "Subscription: $($azcontext.subscription.name)" -Verbose
    Write-Verbose "Resource Group: $ResourceGroupName" -Verbose
    Write-Verbose "Location: $($rg.Location)" -Verbose

    # Get that date range
    $days = ($days + $offsetDays)
    $Date = Get-Date

    $startDate = $Date.AddDays(-$days)
    $endDate = $Date.AddDays(-$offsetDays)

    $startDateString = $startDate.ToString('yyyy-MM-ddTHH:mm:ssZ')
    $endDateString = $endDate.ToString('yyyy-MM-ddTHH:mm:ssZ')
    Write-Verbose "Period: $($startDateString)) to $($endDateString)" -Verbose


    Write-Verbose "Querying cost data for $($scope) (this may take a moment)..." -Verbose

    # Create query for cost data using hashtable structure
    $aggregation = @{
        totalCost = @{
            name = 'PreTaxCost'
            function = 'Sum'
        }
    }

    $grouping = @(
        @{
            type = 'Dimension'
            name = 'ResourceId'
        },
        @{
            type = 'Dimension'
            name = 'ResourceType'
        },
        @{
            type = 'Dimension'
            name = 'MeterCategory'
        },
        @{
            type = 'Dimension'
            name = 'MeterSubCategory'
        }
    )

    <#
    Try to include "uptime" for the resources as part of this query.
    
    #>


    # Execute the query
    $costData = Invoke-AzCostManagementQuery -Scope $scope -Type 'Usage' -Timeframe 'Custom' -TimePeriodFrom $startDateString -TimePeriodTo $endDateString -DatasetGranularity 'None' -DatasetAggregation $aggregation -DatasetGrouping $grouping

    if (!$costData -or !$costData.Row -or $costData.Row.Count -eq 0) {
        Write-Verbose "--> No cost data found for scope - $($scope) - in the last $days days." -Verbose
        Write-Verbose "--> Note: Cost data may have a delay of up to 24-48 hours." -Verbose
        exit
    }

    Write-Verbose "Retrieved $($costData.Row.Count) cost records." -Verbose

    # Parse the results
    $columnNames = $costData.Column.Name
    $report = $costData.Row | ForEach-Object {
        $row = $_
        $rowData = @{}
        for ($i = 0; $i -lt $columnNames.Count; $i++) {
            $rowData[$columnNames[$i]] = $row[$i]
        }
        
        $resourceId = $rowData['ResourceId']
        $resourceName = if ($resourceId) { ($resourceId -split '/')[-1] } else { 'N/A' }
        
        [PSCustomObject] @{
            ResourceName = $resourceName
            ResourceType = $rowData['ResourceType']
            MeterCategory = $rowData['MeterCategory']
            MeterSubCategory = $rowData['MeterSubCategory']
            Cost = [decimal]$rowData['PreTaxCost']
            Currency = $rowData['Currency']
            ResourceId = $resourceId
        }
    }

    # Filter out zero-cost entries and sort
    $sortedReport = $report | Where-Object { $_.Cost -gt 0 } | Sort-Object -Property Cost -Descending

    if ($sortedReport.Count -eq 0) {
        Write-Error "No resources with costs found in the last {0} days." -f $days
        exit
    }

    # $costByType = $sortedReport |
    # Group-Object -Property ResourceType |
    # ForEach-Object {
    # [PSCustomObject]@{
    # ResourceType = $_.Name
    # Count = $_.Count
    # TotalCost = ($_.Group | Measure-Object -Property Cost -Sum).Sum
    # Currency = $_.Group[0].Currency
    # }
    # } |
    # Sort-Object -Property TotalCost -Descending

    # $costByType

    return $sortedReport
}