Powershell ForEach循环为什么会产生不正确的输出

huangapple go评论72阅读模式
英文:

Why Powershell ForEach loop gives incorrect output

问题

我计划生成一份关于我们应用程序代理版本之一的报告,所以我想编写一个流程,该流程将遍历每个订阅中的每个虚拟机并将自定义脚本扩展部署到每个虚拟机资源,然后检索CSE输出值并将其放入表格中,之后我想将其作为CSV文件发送到电子邮件中。脚本代码如下:

# ... 变量初始化已省略

$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"

        # 这个变量解析有效,并列出所有虚拟机
        $cseOutput = "test"

        # 这不起作用,只列出了最初的最后一个虚拟机实例
        $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

CSV文件只包含最后一个输入,但是当我在循环内部检查$hash变量的内容时,它显示了每个虚拟机及其相应值的列表。我已经尝试了好几天,但仍然无法得到我想要的结果。我还使用了数组对象,但据我所知,这不是一个足够的方法,因为它是一个非常耗时的方法,但结果仍然是一样的 - CSV文件只包含最后一个输入。我应该如何修改脚本以获得我想要的结果 - 即包含所有订阅中所有虚拟机名称和CSE子状态消息的CSV文件的两列。

英文:

I'm planning to generate a report on one of our app agent version, so I want to write a pipeline, that will go through every VM in every subscription and deploy a Custom Script Extension to every VM resource, retrieve CSE output value and put in into table, which later I want to send as a CSV file in email. Script code looks like this:

# ... 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

CSV file only consists a last input, but when I check within the loop the content of a $hash variable, it shows a list of every VM and it's coressponding value. I've been trying to figure this out for days and still can't get a result I want. I also used an array object, but afair it's not really sufficient way of doing it because it's a very process time consuming method, yet still result was the same - csv file only consisted of last input. How should I modify script to get the result I want - being a CSV file with two columns - list of all vm names from all subscriptions and cse substatus message.

答案1

得分: 1

**更新**:

* `$cseOutput` 的值中存在*隐藏控制字符*,导致了问题,通过使用 `$cseOutput = $cseOutput -replace ''\W''` 将其消除解决了问题。

  * 请注意,这个特定的基于正则表达式的 [`-replace`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Comparison_Operators#replacement-operator) 操作会从字符串中移除所有不是_单词_字符的字符,即不是字母数字字符或 `_` 的任何字符。

  * 要限制移除仅限于_控制字符_和_隐藏格式字符_(参见[正则表达式中的字符类](https://docs.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#SupportedUnicodeGeneralCategories)),请使用:  
    `$cseOutput = $cseOutput -replace ''[\p{Cc}\p{Cf}]''`

---

#### 原始答案:

您的症状有点神秘:如果哈希表中有多个条目,您*应该*看到多行,尽管列名是固定的 `"Name","Key","Value"`;尝试使用 `@{ one = 1; two = 2; three = 3 }.GetEnumerator() | ConvertTo-Csv` 作为一个快速示例。

---

> 我应该如何修改脚本以获得我想要的结果 - 一个包含所有订阅的所有虚拟机名称和 cse 子状态消息的 CSV 文件。

注意:**问题已更新,包含以下代码,并进行了修改。**

在您的 `foreach` 循环中,发出具有所需 CSV 列名的属性的 `[pscustomobject]` 实例,让 PowerShell 将它们收集到一个数组中 - 或者更好地,切换到 `ForEach-Object` 并将这些对象直接管道到 `Export-Csv`
```shell
# ... 变量初始化省略

$subscriptions |
  ForEach-Object {
    Select-AzSubscription -Subscription $_.Name
    $vms = Get-AzVm -Status | Where-Object { $_.StorageProfile.OsDisk.OsType -eq ''Windows'' -and $_.PowerState -eq ''VM running'' }
    forEach ($vm in $vms) {
      $CSEvalidation = Get-AzVmExtension -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -ErrorAction Continue | Where-Object { $_.Extensiontype -like ''CustomScript*'' } 
      if ($CSEvalidation -ne $null) {
        $null = Remove-AzVmExtension -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -Name $CSEvalidation.name -Force -ErrorAction Continue
      }

      $null = 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 Continue

      $cseOutput = ((Get-AzVm -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -Status).Extensions | where { $_.Type -eq ''Microsoft.Compute.CustomScriptExtension'' }).SubStatuses.Message[0]
      
      # 构造并输出一个具有所需属性的 [pscustomobject] 实例。属性名称将成为 CSV 列名。
      [pscustomobject] @{
        VmName = $vm.Name
        CseSubstatus = $cseOutput
      }

    }
  } | 
  Export-Csv -Delimiter '';'' -NoTypeInformation -Path $(Pipeline.Workspace)\***.csv 

<details>
<summary>英文:</summary>
&lt;!-- language-all: sh --&gt;
**Update**: 
* *Hidden control characters* in the value of `$cseOutput` turned out to cause the problem, and eliminating them with `$cseOutput = $cseOutput -replace &#39;\W&#39;` solved it.
* Note that this specific regex-based [`-replace`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Comparison_Operators#replacement-operator) operation removes all characters that aren&#39;t _word_ characters from the string, i.e. anything isn&#39;t either an alphanumeric character or `_`.
* To limit removal to _control characters_ and _hidden format characters_ only (see [Character classes in regular expressions](https://docs.microsoft.com/en-us/dotnet/standard/base-types/character-classes-in-regular-expressions#SupportedUnicodeGeneralCategories)), use:  
`$cseOutput = $cseOutput -replace &#39;[\p{Cc}\p{Cf}]&#39;` 
---
#### Original answer:
Your symptom is a bit of mystery: If the hashtable has multiple entries, you *should* see multiple rows, albeit with fixed column names `&quot;Name&quot;,&quot;Key&quot;,&quot;Value&quot;`; try with ` @{ one = 1; two = 2; three = 3 }.GetEnumerator() | ConvertTo-Csv` as a quick example.
---
&gt;  How should I modify script to get the result I want - being a CSV file with two columns - list of all vm names from all subscriptions and cse substatus message.
Note: **The question has since been updated to include the code below, with modifications.**
Emit `[pscustomobject]` instances with properties named for the desired CSV column names from your `foreach` loop and let PowerShell collect them in an array for you - or, preferable - switch to `ForEach-Object` and pipe those objects directly to `Export-Csv`:

... variable initializations omitted

$subscriptions |
ForEach-Object {
Select-AzSubscription -Subscription $.Name
$vms = Get-AzVm -Status | Where-Object { $
.StorageProfile.OsDisk.OsType -eq 'Windows' -and $.PowerState -eq 'VM running' }
forEach ($vm in $vms) {
$CSEvalidation = Get-AzVmExtension -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -ErrorAction Continue | Where-Object { $
.Extensiontype -like 'CustomScript*' }
if ($CSEvalidation -ne $null) {
$null = Remove-AzVmExtension -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -Name $CSEvalidation.name -Force -ErrorAction Continue
}

  $null = Set-AzVMCustomScriptExtension -TypeHandlerVersion 1.10 `
-ResourceGroupName $vm.resourceGroupName `
-VMName $vm.Name `
-FileName $fileName `
-ContainerName $containerName `
-StorageAccountName $storageAccountName `
-StorageAccountKey $storageAccountKey `
-Run $fileName `
-Location &#39;West Europe&#39; `
-Name &#39;CustomScriptExtension&#39; `
-ErrorAction Continue
$cseOutput = ((Get-AzVm -VmName $vm.name -ResourceGroupName $vm.resourceGroupName -Status).Extensions | where { $_.Type -eq &#39;Microsoft.Compute.CustomScriptExtension&#39; }).SubStatuses.Message[0]
# Construct and output a [pscustomobject] instance with the desired
# properties. The property names will become the CSV colum names.
[pscustomobject] @{
VmName = $vm.Name
CseSubstatus = $cseOutput
}
}

} |
Export-Csv -Delimiter ';' -NoTypeInformation -Path $(Pipeline.Workspace)***.csv


</details>

huangapple
  • 本文由 发表于 2023年6月5日 23:24:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76407891.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定