Private/Invoke-AACCostBatch.ps1
|
function Invoke-AACCostBatch { <# .SYNOPSIS Runs one Cost Management query over several scopes at once - up to -ThrottleLimit in flight - and returns each scope's rows as objects. .DESCRIPTION The parallel counterpart of Invoke-AACCostQuery: the same body for each -Scope (a subscription, '/subscriptions/<id>', or a management group, '/providers/Microsoft.Management/managementGroups/<id>'), following nextLink paging, with the column/row arrays turned into objects - @{ Cost = 12.3; ResourceId = '...'; Currency = 'USD' }. Months arrive as [datetime], as ConvertFrom-Json reads ISO dates. Cost Management allows only a few queries a minute per scope and tenant, so 3 at a time; throttled requests wait as long as its x-ms-ratelimit-microsoft.costmanagement-*-retry-after headers ask (Invoke-AACHttpBatch). Returns a hashtable: scope -> @{ Rows; Status ('OK', or why that scope couldn't be read) }. -OnProgress is called with (scope, status, done, total) as each finishes. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [string[]] $Scope, [Parameter(Mandatory)] [hashtable] $Body, [scriptblock] $OnProgress, [ValidateRange(1, 8)] [int] $ThrottleLimit = 3 ) $json = $Body | ConvertTo-Json -Depth 10 $rows = @{} $requests = @(foreach ($item in @($Scope | Where-Object { $_ } | Select-Object -Unique)) { $rows[$item] = [System.Collections.Generic.List[object]]::new() @{ Key = $item; Uri = "$($item.TrimEnd('/'))/providers/Microsoft.CostManagement/query?api-version=2023-11-01"; Body = $json } }) $onResponse = { param($Key, [string] $Content) $page = ConvertFrom-Json -InputObject $Content -Depth 100 $properties = Get-AACPropertyValue -InputObject $page -Name 'properties' $columns = @(Get-AACPropertyValue -InputObject $properties -Name 'columns' | ForEach-Object { $_.name }) foreach ($row in @(Get-AACPropertyValue -InputObject $properties -Name 'rows')) { if ($null -eq $row) { continue } $item = [ordered]@{} for ($i = 0; $i -lt $columns.Count; $i++) { $item[$columns[$i]] = $row[$i] } $rows[$Key].Add([pscustomobject]$item) } $next = Get-AACPropertyValue -InputObject $properties -Name 'nextLink' if ($next) { @{ Uri = [string]$next; Body = $json } } } $onDone = { param($Key, $Failure, $Done, $Total) if ($OnProgress) { & $OnProgress $Key $(if ($Failure) { $Failure } else { 'OK' }) $Done $Total } } $failures = Invoke-AACHttpBatch -Request $requests -OnResponse $onResponse -OnDone $onDone -ThrottleLimit $ThrottleLimit $result = @{} foreach ($key in @($rows.Keys)) { $result[$key] = if ($failures[$key]) { @{ Rows = @(); Status = [string]$failures[$key] } } else { @{ Rows = $rows[$key].ToArray(); Status = 'OK' } } } $result } |