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

时间:2017-07-13 19:46:16

标签: arrays powershell scalar cmdlets cim

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

$Disks = Get-Disk
$Disks.Count

不归还任何东西。我发现我可以将它转换为[数组],这使得它按预期返回.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转换为数组吗?我是以完全错误的方式解决这个问题吗?

2 个答案:

答案 0 :(得分:7)

在PSv3 +中,通过统一处理标量和集合,任何对象 - 甚至$null - 应该拥有.Count属性(以及$null除外,应支持使用[0]进行索引。

任何出现支持上述内容的 的对象都应被视为 bug

例如,不按这些规则播放的[pscustomobject]个实例是known bug

由于我不知道所述错误是否与[Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk]输出的Get-Disk个实例有关,而且Get-Disk至少是当前的@(...)实例 - 仅在 Windows PowerShell ,我建议您在uservoice.com上单独提交错误。

使用array-subexpression运算符.Count ::

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

  • 以防标量对象碰巧拥有拥有 @(...)属性

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

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

此外, [Array] ...@($null) 通常不等同于 ,因为PetSerAl是一个有用的示例评论问题证明;适应他的一个例子:

$null返回单项数组,其唯一元素为[Array] $null,而$null无效(保留@())。

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

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

@()的目的及其运作方式:

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

更确切地说,,的行为如下:向PetSerAl提供大量帮助的提示。

  • PSv5.1 + 中,使用使用 @()使用直接构建数组的表达式 (数组表达式运算符)优化@(1, 2)

    • 例如,1, 2@(, 1)相同,, 1,相同。

    • 对于使用 System.Object[]构造的数组 - 产生@( ..., ..., ...)数组 - 这种优化很有帮助,因为它节省了不必要的步骤首先展开该阵列,然后重新包装(见下文) 据推测,这种优化是由于使用@()来构造数组的广泛且以前效率低下的做法所促成的,源于错误地认为构造数组需要[int[]]

    • 但是,在仅限Windows PowerShell v5.1 中,使用构建具有特定类型的阵列时,也会应用优化强制转换 ,例如@([int[]] (1, 2)).GetType().Name(PowerShell Core 中的行为已得到纠正,旧的Windows PowerShell版本不受影响);例如,
      Int32[]会产生@()。这是System.Object[]返回其他而不是@([int[]] (1, 2))[-1] = 'foo'的唯一情况,并假设它总是会导致意外错误和副作用 ;例如: -
      $a = [int[]] (1, 2); $b = @([int[]] $a)休息 @(...)意外地未创建数组 - 请参阅this GitHub issue

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

    • 如果结果是项目/包含项目,则结果包裹在单个项目中元素/空数组@('foo').GetType().Name

      • 例如,Object[]产生@('foo').Count1产生'foo'.Count(但如上所述,在PSv3 +中,您可以直接使用@( & { } ).Count) 。
        0产生[System.Management.Automation.Internal.AutomationNull]::Value(执行空脚本块输出" null集合" (@()

      • 警告:围绕New-Object调用的 @(New-Object System.Collections.ArrayList).Count创建一个数组/集合输出数组/集合包裹在单个 - 元素外部数组

        • 1产生System.Object[] - 空数组列表包含在单个元素New-Object实例中。
        • 原因是@()由于是命令(例如 cmdlet 调用)而不是需要解包,导致@([system.collections.arraylist]::new()).Count只看到单个项(恰好是一个数组/集合),因此它包含在一个单项数组中。

        • 当您使用表达式构建数组/集合时,会发生 not ,这可能令人困惑,因为表达式是' s输出解包(展开):

          • 0收益@();表达式输出一个空集合,该集合展开为不包含任何项目的结果,System.Object[]重新打包为空(...)数组。
            请注意,在 PSv3 + 中,只需使用一组额外的括号(New-Object)和New-Object - 这会转换@((New-Object System.Collections.ArrayList)).Count 命令表达式 - 会产生相同的结果:
            0也会产生[System.Object[]]
    • 如果结果包含多个,则这些项将作为数组返回,(通常在WinPSv5.1中)并非总是 - 请参阅上文)作为常规PowerShell数组($arr = [int[]] (1, 2); @($arr)

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

答案 1 :(得分:2)

@mklement0 有一个很好的答案,但要补充一点:如果您的脚本(或调用您的脚本)具有 Set-StrictMode,自动 .Count.Length 属性将停止工作:

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

为了安全起见,您可以在检查长度之前将任何未知变量包装在数组 @(...) 中:

$ $result = Get-Something
$ @($result).Length
1