9

我正在尝试执行一些简单的 if 语句,但所有基于 [Microsoft.Management.Infrastructure.CimInstance] 的较新 cmdlet 似乎都没有公开 .count 方法?

$Disks = Get-Disk
$Disks.Count

不返回任何东西。我发现我可以将其转换为 [array],这使它按预期返回 .NET .count 方法。

[Array]$Disks = Get-Disk
$Disks.Count

这无需直接将其转换为先前 cmdlet 的数组即可工作:

(Get-Services).Count

解决此问题的推荐方法是什么?

一个不起作用的例子:

$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
  If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
   Else If ($PageDisk.Count -eq 1) { Do X }

选项 A(转换为数组):

[Array]$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
  If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
   Else If ($PageDisk.Count -eq 1) { Do X }

选项 B(使用数组索引):

 $PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
  If ($PageDisk[0] -eq $Null) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk[1] -ne $Null) {Write-Host "Too many drives found, manually select it."}
   Else If (($PageDisk[0] -ne $Null) -and (PageDisk[1] -eq $Null)) { Do X }

选项 C(数组)-感谢 @PetSerAl :

$PageDisk = @(Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)})
  If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
   Else If ($PageDisk.Count -eq 1) { Do X }

基于 CIM 的 cmdlet 不公开 .Count 方法的原因是什么?处理此问题的推荐方法是什么?选项 B 对我来说似乎很复杂,而且很难阅读。选项 A 有效,但 powershell 不应该为我将其转换为数组吗?我是否以完全错误的方式解决这个问题?

4

2 回答 2

14

在 PSv3+ 中,通过统一处理标量和集合,任何对象 - 甚至$null-都应该有一个.Count属性(并且,除了$null,应该支持使用 索引[0])。

任何支持上述内容的对象都应被视为错误;例如:

由于我不知道所述错误是否与输出的[Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk]实例有关,并且至少目前仅在Windows PowerShell中可用,因此我鼓励您在uservoice.com上提交单独的错误。Get-DiskGet-Disk

需要使用数组子表达式运算@(...)

  • 作为手头的错误/错误的解决方法Set-StrictMode -Version 2

  • 如果标量对象碰巧有自己的 .Count属性


通常,如果您确实需要确保某个东西是一个数组,使用@(...)而不是[Array] .../[object[]] ... -@()是 PowerShell 惯用的,更简洁,语法更容易。

也就是说,鉴于@()技术上创建了现有数组的(浅)副本[Array],您可能更喜欢在处理潜在的大型数组时。

此外,@(...)并且[Array] ...通常不是等效的,正如PetSerAl在对该问题的评论中的有用示例所证明的那样;改编他的一个例子:

@($null)返回一个单项数组,其唯一的元素是$null,而[Array] $null没有效果(保持$null)。

的这种行为@()与其目的一致(见下文):因为$null它不是一个数组,@()所以将其包装在一个中(导致一个[System.Object[]]实例$null作为唯一元素)。

在 PetSerAl 的其他示例中,@()使用 -created 数组和集合的行为New-Object- 可能令人惊讶 - 见下文。


目的@(...)和工作原理:

简单地说,数组子表达式运算符目的是@()确保表达式/命令的结果被视为数组,即使它碰巧是一个标量(单个对象)。:

  • @(...) [object[]]即使只有一个输出对象,按原样收集封闭命令的输出/封闭表达式的枚举输出在一个 -总是新的-数组中。

  • @(...)数组字面量不需要(在 v5.1+ 中它已被优化) -使用,数组构造函数运算符 本身通常就足够了,例如,'foo', 'bar'而不是@('foo', 'bar')- 但它在以下情况下很有用

    • 创建一个数组@():)

    • 创建一个单元素数组 - 例如- 这比原本需要的一元形式@('foo')更容易阅读- 例如,, 'foo'

    • 为了语法上的方便:将概念上的数组文字分布在多行中,而不必使用,分隔元素,也不必将命令包含在(...); 例如:

      @(
        'one'
        Write-Output two
      )
      
  • 陷阱

    • @()不是数组构造函数,而是“保证人”:因此,@(@(1,2))不会创建嵌套数组:

      • @(@(1, 2))实际上与@(1, 2)(并且只是1, 2)相同。事实上,每增加@()一个都是昂贵的 no-op,因为它只是创建了前一个数组输出的副本
      • 使用数组构造器运算符,的一元形式,构造嵌套数组:
        , (1, 2)
    • $null被 视为单个对象@()因此产生一个带有元素的单元素数组$null

      • @($null).Count1
    • 将单个数组作为一个整体输出的命令调用会导致嵌套数组:

      • @(Write-Output -NoEnumerate 1, 2).Count1
    • 表达式中,将任何类型的集合包装在@() 枚举中,并且总是将元素收集在 (new) 中[object[]] array

      • @([System.Collections.ArrayList] (1, 2)).GetType().Name返回'Object[]'

如果需要,请继续阅读以获取更多详细信息。


详情

@()行为如下:向 PetSerAl 致敬,感谢他的广泛帮助。

  • PSv5.1+(Windows PowerShell 5.1 和 PowerShell [Core] 6+)中,使用直接构造数组的表达式,,使用数组构造函数运算符优化@()

    • 例如,@(1, 2)与 just 相同1, 2,并且@(, 1)与 just 相同, 1

    • 在使用just ,构造的数组的情况下- 这会产生一个System.Object[]数组 - 这种优化很有帮助,因为它节省了首先展开该数组然后重新打包它的不必要步骤(见下文)。
      据推测,这种优化是由使用构造数组的广泛且先前低效的实践@( ..., ..., ...)引起的,源于构造数组@()所需的错误信念。

    • 但是,在Windows PowerShell v5.1中,在使用强制类型转换构造具有特定类型的数组时,意外也应用了优化,例如(在PowerShell [Core] 6+ 和旧 Windows PowerShell 版本中[int[]],该行为已得到纠正受影响);例如,产量。这是唯一会返回其他内容的情况,并且假设它总是返回可能会导致意外错误和副作用;例如:休息。意外地没有创建一个新的
      @([int[]] (1, 2)).GetType().NameInt32[]@()System.Object[]
      @([int[]] (1, 2))[-1] = 'foo'
      $a = [int[]] (1, 2); $b = @([int[]] $a)数组 - 请参阅此 GitHub 问题

  • 否则:如果里面的(第一个)语句@(...)是一个恰好是一个集合的表达式,则该集合被枚举命令的(通常是一对一的流式传输)输出按原样收集;在任何一种情况下,对象的结果计数都决定了行为:

    • 如果结果是单个项目/不包含任何项目,则结果将包装在类型为 的单元素/空数组中[System.Object[]]

      • 例如,@('foo').GetType().Name产量Object[]@('foo').Count产量1(尽管如前所述,在 PSv3+ 中,您可以'foo'.Count直接使用)。
        @( & { } ).Count产生0(执行一个空的脚本块输出一个“空集合”([System.Management.Automation.Internal.AutomationNull]::Value

      • 警告@()围绕New-Object创建数组/集合的调用输出该数组/集合包装在单元素外部数组中。

        • @(New-Object System.Collections.ArrayList).Countyield 1- 空数组列表被包装在一个单元素System.Object[]实例中。

        • 原因是New-Object,由于是一个命令(例如cmdlet调用),它不受枚举(展开)的影响,导致@()只能看到一个项目(恰好是一个数组/集合),因此它包含在单项数组。

        • 可能令人困惑的是,当您使用表达式构造数组/集合时不会发生这种情况,因为表达式的输出是枚举的(展开,展开):

          • @([system.collections.arraylist]::new()).Count产量0;该表达式输出一个被枚举的空集合,并且由于需要枚举,因此@()创建一个空System.Object[]数组。
            请注意,在PSv3+中,简单地使用一组额外的括号 ( (...)) 和New-Object- 将New-Object 命令转换为表达式- 将产生相同的结果:也
            @((New-Object System.Collections.ArrayList)).Count产生0了。
    • 如果结果包含多个项目,这些项目将作为常规 PowerShell 数组 ( [System.Object[]])返回;例如:

      • 使用命令
        • $arr = @(Get-ChildItem *.txt)从数组Get-ChildItem中收集一对一的流输出System.Object[]
      • 用一个表达式
        • $arr = [int[]] (1, 2); @($arr) 枚举 [int[]]数组$arr,然后将元素重新打包System.Object[]数组。

        • 请注意此过程的低效率类型保真度的潜在损失:原始数组被枚举并收集在一个始终为 type的System.Object[]数组中;有效的替代方法是强制转换为[array](也适用于命令):[array] $result = ...

于 2017-07-13T21:51:50.420 回答
2

@mklement0 has a great answer, but one thing to add: if your script (or the script calling yours) has Set-StrictMode, the automatic .Count and .Length properties stop working:

$ Set-StrictMode -off
$ (42).Length
1
$ Set-StrictMode -Version Latest
$ (42).Length
ParentContainsErrorRecordException: The property 'Length' cannot be found on this object. Verify that the property exists.

To be safe, you can wrap any unknown variable in an array @(...) before checking the length:

$ $result = Get-Something
$ @($result).Length
1
于 2021-02-11T21:45:00.130 回答