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

PowerShell ForEach循环输出异常:仅生成最后一条VM数据

问题

我需要生成一份应用代理版本报告,编写PowerShell脚本遍历所有Azure订阅中的Windows运行状态VM,为每台VM部署Custom Script Extension(CSE),获取CSE的子状态消息后整理成表格并导出为CSV文件。但目前导出的CSV仅包含最后一台VM的数据,虽然在循环内查看时能显示所有VM及其对应值。尝试过用数组存储结果,但不仅结果相同,还增加了耗时。脚本代码如下:

# ... variable initializations omitted

$subscriptions | ForEach-Object {
  Select-AzSubscription -Subscription $_.Name
  $vms = Get-AzVm -Status | Where-Object {$_.StorageProfile.OsDisk.OsType -eq "Windows" -and $_.Name -notlike "azbecdns*" -and $_.Name -notlike "SF160SV0*" -and $_.PowerState -eq "VM running"}
  forEach ($vm in $vms) {

      $CSEvalidation = Get-AzVmExtension -VmName $vm.name -ResourceGroupName $vm.resourceGroupName | where-object {$_.Extensiontype -like "CustomScript*"} 
      if ($CSEvalidation -ne $null)
      {
        Remove-AzVmExtension -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -Name $CSEvalidation.name -Force -ErrorAction Continue
      }
      Set-AzVMCustomScriptExtension -TypeHandlerVersion 1.10 `
              -ResourceGroupName $vm.resourceGroupName `
              -VMName $vm.Name `
              -FileName $fileName `
              -ContainerName $containerName `
              -StorageAccountName $storageAccountName `
              -StorageAccountKey $storageAccountKey `
              -Run $fileName `
              -Location "West Europe" `
              -Name "CustomScriptExtension"

      #this var parse works and lists all vms
      $cseOutput = "test"

      #this doesn't work and only list last vm instance like in the very beginning
      $cseOutput = ((Get-AzVm -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -Status).Extensions | Where {$_.Type -eq 'Microsoft.Compute.CustomScriptExtension'}).SubStatuses.Message[0]

      [PSCustomObject] @{
          VmName = $vm.name
          AgentVer = $cseOutput
        }
  }
} | Select-Object -Property VmName, AgentVer | Where-Object {$_.VmName -ne $null} | export-csv -NoTypeInformation -delimiter ';' -Path $(Pipeline.Workspace)\***.csv
解决方案

问题核心在于Set-AzVMCustomScriptExtension是异步操作,部署后立即获取状态会拿到未更新的信息;同时原订阅切换方式可能导致部分结果对象未正确传入导出管道。以下是修改后的脚本及关键调整:

关键修改点

  • 用Set-AzContext替代Select-AzSubscription,确保订阅上下文切换更可靠
  • 添加CSE部署后的等待逻辑,轮询状态直到完成,避免获取空值
  • 直接用Get-AzVmExtension获取CSE状态,无需重新拉取整个VM,提升效率
  • 增加超时判断,避免脚本因异常无限等待

修改后的脚本

# ... variable initializations omitted

$subscriptions | ForEach-Object {
    # 切换订阅上下文
    Set-AzContext -Subscription $_.Name | Out-Null
    $vms = Get-AzVm -Status | Where-Object {
        $_.StorageProfile.OsDisk.OsType -eq "Windows" -and 
        $_.Name -notlike "azbecdns*" -and 
        $_.Name -notlike "SF160SV0*" -and 
        $_.PowerState -eq "VM running"
    }

    foreach ($vm in $vms) {
        # 清理已存在的CSE
        $existingCSE = Get-AzVmExtension -VmName $vm.Name -ResourceGroupName $vm.ResourceGroupName -ErrorAction SilentlyContinue | 
                       Where-Object {$_.ExtensionType -like "CustomScript*"}
        if ($existingCSE) {
            Remove-AzVmExtension -VmName $vm.Name -ResourceGroupName $vm.ResourceGroupName -Name $existingCSE.Name -Force -ErrorAction Continue | Out-Null
        }

        # 部署新的CSE
        Set-AzVMCustomScriptExtension -TypeHandlerVersion 1.10 `
                -ResourceGroupName $vm.ResourceGroupName `
                -VMName $vm.Name `
                -FileName $fileName `
                -ContainerName $containerName `
                -StorageAccountName $storageAccountName `
                -StorageAccountKey $storageAccountKey `
                -Run $fileName `
                -Location "West Europe" `
                -Name "CustomScriptExtension" `
                -ErrorAction Stop | Out-Null

        # 等待CSE部署完成,最多等待5分钟
        $waitTimeout = 300
        $startTime = Get-Date
        $cseStatus = $null
        do {
            Start-Sleep -Seconds 10
            $cseStatus = Get-AzVmExtension -VmName $vm.Name -ResourceGroupName $vm.ResourceGroupName -Name "CustomScriptExtension" -Status -ErrorAction SilentlyContinue
            $elapsedTime = (Get-Date) - $startTime
        } while ($cseStatus -eq $null -or $cseStatus.ProvisioningState -ne "Succeeded" -and $elapsedTime.TotalSeconds -lt $waitTimeout)

        # 获取CSE子状态消息
        $cseOutput = if ($cseStatus -and $cseStatus.SubStatuses) {
            $cseStatus.SubStatuses.Message[0]
        } else {
            "CSE部署超时或失败"
        }

        # 输出结果对象到管道
        [PSCustomObject]@{
            VmName        = $vm.Name
            AgentVer      = $cseOutput
            Subscription  = $_.Name # 可选:添加订阅名称便于区分不同订阅的VM
        }
    }
} | Select-Object -Property VmName, AgentVer, Subscription | # 若不需要订阅列可移除
  Where-Object {$_.VmName -ne $null} | 
  Export-Csv -NoTypeInformation -Delimiter ';' -Path "$(Pipeline.Workspace)\app_agent_report.csv"

说明

  1. 等待逻辑:通过轮询CSE的ProvisioningState直到变为Succeeded或超时,确保获取到的是最新的部署结果
  2. 订阅切换:Set-AzContext比Select-AzSubscription更稳定,避免上下文切换不彻底导致的资源获取异常
  3. 效率优化:直接获取CSE扩展的状态,减少不必要的API调用,提升脚本运行速度
  4. 可靠性:添加超时判断,同时对部署失败/超时的情况给出明确提示,避免结果缺失

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:57:54