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"
说明
- 等待逻辑:通过轮询CSE的
ProvisioningState直到变为Succeeded或超时,确保获取到的是最新的部署结果 - 订阅切换:
Set-AzContext比Select-AzSubscription更稳定,避免上下文切换不彻底导致的资源获取异常 - 效率优化:直接获取CSE扩展的状态,减少不必要的API调用,提升脚本运行速度
- 可靠性:添加超时判断,同时对部署失败/超时的情况给出明确提示,避免结果缺失
内容的提问来源于stack exchange,提问作者lubierzca
相关产品推荐
相关产品推荐

