没有'.Count'属性的对象 - 使用@()(数组子表达式运算符)与[Array]强制转换

Cob*_*ode 6 arrays powershell cmdlets scalar cim

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

$Disks = Get-Disk
$Disks.Count
Run Code Online (Sandbox Code Playgroud)

不归还任何东西.我发现我可以将它转换为[数组],这使得它返回一个.NET .count方法,如预期的那样.

[Array]$Disks = Get-Disk
$Disks.Count
Run Code Online (Sandbox Code Playgroud)

这可以直接将其作为以前cmdlet的数组投射:

(Get-Services).Count
Run Code Online (Sandbox Code Playgroud)

推荐的解决方法是什么?

一个不起作用的例子:

$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 }
Run Code Online (Sandbox Code Playgroud)

选项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 }
Run Code Online (Sandbox Code Playgroud)

选项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 }
Run Code Online (Sandbox Code Playgroud)

选项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 }
Run Code Online (Sandbox Code Playgroud)

基于CIM的cmdlet不暴露.Count方法的原因是什么?建议的方法是什么?选项B对我来说似乎很复杂,很难阅读.选项A有效,但是不应该将PowerShell转换为数组吗?我是以完全错误的方式解决这个问题吗?

mkl*_*nt0 9

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

任何支持上述内容的对象都应被视为错误.

例如,[pscustomobject]不遵守这些规则的实例是已知错误.

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

数组子表达式运算符的使用@(...)唯一必要的:

  • 作为上述错误的解决方法.

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


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

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

此外,@(...)并且[Array] ...通常不等同,因为PetSerAl在对该问题的评论中提供了有用的示例; 适应他的一个例子:

@($null)返回单项数组,其唯一元素是$null,[Array] $null但没有效果(停留$null).

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

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


它的目的@(...)和工作方式:

松散地说,array-subexpression运算符目的@()确保表达式/命令的结果被视为数组,即使它恰好是标量(单个对象).

更准确地说,@()表现如下:给PetSerAl的帽子提示他的广泛帮助.

  • PSv5.1 +,使用一个表达式直接构建的阵列使用,(数组表达式运算符)优化@():

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

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

    • 然而,在Windows PowerShell中仅V5.1优化也构建阵列与一个应用时,特定类型的使用铸铁,如[int[]](行为在PowerShell中得到纠正核心及以上的Windows PowerShell版本不受影响); 例如,
      @([int[]] (1, 2)).GetType().Name收益率Int32[].这是其中唯一的情况@()返回的东西其他System.Object[],并假设它总是会导致意想不到的错误和副作用 ; 例如:
      @([int[]] (1, 2))[-1] = 'foo'休息.
      $a = [int[]] (1, 2); $b = @([int[]] $a)意外地没有创建数组 - 请参阅此GitHub问题.

  • 否则:如果内部的(第一个)语句@(...)表达式,则使用PowerShell的标准集合展开(展开)技术收集它; 但是,命令的输出被解释为原样; 在任何一种情况下,结果对象的数量决定了行为:

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

      • 例如,@('foo').GetType().Name产量Object[]@('foo').Count产量1(尽管如上所述,在PSv3 +中,您可以'foo'.Count直接使用).
        @( & { } ).Countyield 0(执行空脚本块输出"null collection"([System.Management.Automation.Internal.AutomationNull]::Value)

      • 警告:@()New-Object创建数组/集合的调用周围输出包含在单元素外部数组中的数组/集合.

        • @(New-Object System.Collections.ArrayList).Countyield 1- 空数组列表包含在单个元素System.Object[]实例中.
        • 其原因是New-Object,由于是一个命令(如小命令调用)是受到展开,导致@()只看到单个项(这恰好是一个数组/集合),因此,其它包装在单项目数组.

        • 什么可能会产生混淆的是,这并没有当您使用的发生表达,因为表达的输出是构造一个数组/集合,展开(展开):

          • @([system.collections.arraylist]::new()).Count产量0; 表达式输出一个空集合,该集合将展开为不包含任何项目的结果,并@()重新打包为空System.Object[]数组.
            需要注意的是,在PSv3 +,简单地使用额外的括号((...))与New-Object-其将New-Object 命令到一个表达 -将产生相同的结果:
            @((New-Object System.Collections.ArrayList)).Count产生0太.
    • 如果结果包含多个项目,则这些项目将作为数组返回(通常,但在WinPSv5.1中并不总是 - 请参见上文)作为常规PowerShell数组([System.Object[]]):

      • 例如,$arr = [int[]] (1, 2); @($arr)展开[int[]]数组$arr,然后将元素重新打包为System.Object[]数组.
        (如上所述,仅在WinPSv5.1中,如果将数组创建表达式直接放在内部 @(),则实际上会得到一个[int[]]数组.)