arrayspowershellscalarpowershell-cmdletcim

Objects with no '.Count' Property - use of @() (array subexpression operator) vs. [Array] cast


I am trying to perform some simple if statements, but all of the newer cmdlets that are based upon [Microsoft.Management.Infrastructure.CimInstance] don't seem to expose a .count method?

$Disks = Get-Disk
$Disks.Count

Doesn't return anything. I found that I can cast this as an [array], which makes it returns a .NET .count method as expected.

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

This works without directly casting it as an array for previous cmdlets:

(Get-Services).Count

What is the recommended way to get around this?

An example that doesn't work:

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

Option A (Cast as Array):

[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 }

Option B (Use Array Indexes):

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

Option C (Array) -Thanks to @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 }

What is the reason for CIM based cmdlets not exposing the .Count method? What is the recommended way to handle this? Option B seems convoluted to me, and hard to read. Option A works, but shouldn't powershell cast this as an array for me? Am I going about this in entirely the wrong way?


Solution

  • In PSv3+, with its unified handling of scalars and collections, any object - even $null - should have a .Count property (and, with the exception of $null, should support indexing with [0]).

    Any occurrence of an object not supporting the above should be considered a bug; for instance:

    Since I don't know if said bug is related to the [Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk] instances that Get-Disk outputs, and since Get-Disk - at least currently - is only available in Windows PowerShell, I encourage you to file a separate bug on uservoice.com.

    Use of array-subexpression operator @(...) is only necessary:


    Generally,

    Additionally, @(...) and [Array] ... are typically, but not always equivalent, as PetSerAl's helpful examples in a comment on the question demonstrate; to adapt one of his examples:

    @($null) returns a single-item array whose one and only element is $null, whereas [Array] $null has no effect (stays $null).

    This behavior of @() is consistent with its purpose (see below): since $null is not an array, @() wraps it in one (resulting in a [System.Object[]] instance with $null as the only element).

    In PetSerAl's other examples, @()'s behavior with New-Object-created arrays and collections - may be surprising - see below.


    The purpose of @(...) and how it works:

    The purpose of @(), the array-subexpression operator, is, loosely speaking, to ensure that the result of an expression/command is treated as an array, even if it happens to be a scalar (single object).:

    Read on for more detailed information, if needed.


    Details:

    @() behaves as follows: Tip of the hat to PetSerAl for his extensive help.


    [1] For a summary of which types PowerShell considers enumerable - which both excludes select types that do implement IEnumerable and includes one that doesn't - see the bottom section of this answer.