You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure成本管理API PowerShell脚本获取Location及BlobLink失败排查

问题描述

我尝试通过Azure Cost Management API提取使用成本,先在Postman中测试了API的POST请求,流程正常:

POST https://management.azure.com/subscriptions/0000000-0000-0000-0000-00000000/providers/Microsoft.CostManagement/generateCostDetailsReport?api-version=2022-10-01

{
  "metric": "ActualCost",
  "timePeriod": {
    "start": "2020-03-01",
    "end": "2020-03-15"
  }
}

根据文档,POST响应的Location头包含报表查询URI,GET该URI可获取包含Blob下载链接的响应体。但我写的PowerShell脚本批量遍历订阅时,$usageGet1Uri和$blobLink为空,导致Invoke-RestMethod和Invoke-WebRequest因Uri参数为空报错。

脚本内容:

# Step 1: Get mandatory parameters from the user
$startDate = Read-Host "Enter the start date (YYYY-MM-DD):"
$endDate = Read-Host "Enter the end date (YYYY-MM-DD):"

# Step 2: Connect to Azure and get subscriptions
Connect-AzAccount

$subscriptions = Get-AzSubscription | Select-Object -ExpandProperty SubscriptionId

# Step 3: Process each subscription
foreach ($subscriptionId in $subscriptions) {
    # Generate cost details report
    $generateReportUri = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.CostManagement/generateCostDetailsReport?api-version=2022-10-01"

    $reportRequest = @{
        metric = "ActualCost"
        timePeriod = @{
            start = $startDate
            end = $endDate
        }
    }

    $reportResponse = Invoke-RestMethod -Uri $generateReportUri -Method Post -Body ($reportRequest | ConvertTo-Json) -Headers @{ "Authorization" = "Bearer $((Get-AzAccessToken).Token)" }

    # Get usage report URI
    $usageGet1Uri = $reportResponse.Headers.Location

    # Download and rename the usage report file
    $usageReport = Invoke-RestMethod -Uri $usageGet1Uri -Method Get -Headers @{ "Authorization" = "Bearer $((Get-AzAccessToken).Token)" }
    $blobLink = $usageReport.Manifest.blobs.blobLink
    $subscriptionName = (Get-AzSubscription -SubscriptionId $subscriptionId).Name
    $fileName = "$subscriptionName.csv"

    Invoke-WebRequest -Uri $blobLink -OutFile $fileName

    Write-Host "Downloaded and renamed usage report for subscription '$subscriptionName' to '$fileName'."
}

报错信息:

Invoke-RestMethod : Cannot validate argument on parameter 'Uri'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again. At C:\Users\XXXXXX\VScode\POST_method_try.ps1:29 char:43

  • $usageReport = Invoke-RestMethod -Uri $usageGet1Uri -Method Get - ...
    ~~~~~~~~~~~~~
    CategoryInfo : InvalidData: (:) [Invoke-RestMethod], ParameterBindingValidationException
    FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
    Invoke-WebRequest : Cannot validate argument on parameter 'Uri'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again. At C:\Users\XXXXXX\VScode\POST_method_try.ps1:34 char:28
  • Invoke-WebRequest -Uri $blobLink -OutFile $fileName
    ~~~~~~~~~
    CategoryInfo : InvalidData: (:) [Invoke-WebRequest], ParameterBindingValidationException
    FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
错误原因分析
  1. Invoke-RestMethod不返回响应头:默认情况下Invoke-RestMethod只返回API的响应体,不会包含响应头信息,所以$reportResponse.Headers.Location必然为空,需要改用Invoke-WebRequest获取完整响应对象。
  2. 异步操作未等待:生成成本报表是异步操作,POST请求返回Location后不能立刻发起GET请求,必须轮询等待操作状态变为Succeeded,否则GET响应不会包含Blob链接。
  3. Blob链接路径错误:响应中blobs是数组类型,直接取$usageReport.Manifest.blobs.blobLink无法正确获取值,需要指定数组索引。
修复后的脚本
# Step 1: Get mandatory parameters from the user
$startDate = Read-Host "Enter the start date (YYYY-MM-DD):"
$endDate = Read-Host "Enter the end date (YYYY-MM-DD):"

# Step 2: Connect to Azure and get subscriptions
Connect-AzAccount
$subscriptions = Get-AzSubscription | Select-Object SubscriptionId, Name

# Step 3: Reuse access token to avoid repeated calls
$accessToken = (Get-AzAccessToken).Token
$authHeader = @{ "Authorization" = "Bearer $accessToken" }

# Step 4: Process each subscription
foreach ($sub in $subscriptions) {
    $subscriptionId = $sub.SubscriptionId
    $subscriptionName = $sub.Name
    Write-Host "Processing subscription: $subscriptionName ($subscriptionId)"

    # Generate cost details report
    $generateReportUri = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.CostManagement/generateCostDetailsReport?api-version=2022-10-01"
    $reportRequest = @{
        metric = "ActualCost"
        timePeriod = @{
            start = $startDate
            end = $endDate
        }
    } | ConvertTo-Json

    # Use Invoke-WebRequest to get full response including headers
    $postResponse = Invoke-WebRequest -Uri $generateReportUri -Method Post -Body $reportRequest -Headers $authHeader -ContentType "application/json"
    $usageGetUri = $postResponse.Headers["Location"]

    if (-not $usageGetUri) {
        Write-Host "Failed to get report status URI for $subscriptionName"
        continue
    }

    # Wait for the report generation to complete (poll every 10 seconds, max 30 attempts)
    $maxAttempts = 30
    $attemptCount = 0
    $reportStatus = "Running"
    while ($reportStatus -ne "Succeeded" -and $attemptCount -lt $maxAttempts) {
        Start-Sleep -Seconds 10
        $attemptCount++
        $statusResponse = Invoke-RestMethod -Uri $usageGetUri -Method Get -Headers $authHeader
        $reportStatus = $statusResponse.status

        Write-Host "Attempt $attemptCount/$maxAttempts - Report status: $reportStatus"
        if ($reportStatus -eq "Failed") {
            Write-Host "Report generation failed for $subscriptionName : $($statusResponse.error.message)"
            break
        }
    }

    if ($reportStatus -ne "Succeeded") {
        Write-Host "Report generation timed out for $subscriptionName"
        continue
    }

    # Get the final report data with blob link
    $finalReport = Invoke-RestMethod -Uri $usageGetUri -Method Get -Headers $authHeader
    # Blobs are stored in an array, take the first blob (adjust if multiple blobs exist)
    $blobLink = $finalReport.manifest.blobs[0].blobLink

    if (-not $blobLink) {
        Write-Host "No blob link found for $subscriptionName"
        continue
    }

    # Download the report
    $fileName = "$subscriptionName.csv"
    Invoke-WebRequest -Uri $blobLink -OutFile $fileName

    Write-Host "Successfully downloaded report for $subscriptionName to $fileName`n"
}
修复说明
  • 改用Invoke-WebRequest发送POST请求,获取包含Location头的完整响应。
  • 增加轮询逻辑,等待报表生成操作完成(状态变为Succeeded),避免过早发起GET请求。
  • 修正Blob链接提取路径,直接访问数组第一个元素(若有多个Blob可按需遍历)。
  • 复用AccessToken,减少重复调用Get-AzAccessToken的次数,提升效率。
  • 增加错误判断逻辑,避免中间步骤失败导致后续报错。

内容的提问来源于stack exchange,提问作者Mantra Reborn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:40:32