Note: This answer complements Mathias R. Jessen's helpful answer.
Counting the number of objects returned by a command:
Mathias' answer shows a robust, PowerShell v2-compatible solution based on the array sub-expression operator, @()
.
# @() ensures that the output of command ... is treated as an array,
# even if the command emits only *one* object.
# You can safely call .Count (or .Length) on the result to get the count.
@(...).Count
In PowerShell v3 or higher, you can treat scalars like collections, so that using just (...).Count
is typically enough. (A scalar is a single objects, as opposed to a collections of objects).
# Even if command ... returns only *one* object, it is safe
# to call .Count on the result in PSv3+
(...).Count
These methods are typically, but not always interchangeable, as discussed below.
Choose @(...).Count
, if:
- you must remain PSv2-compatible
- you want to count output from multiple commands (separated with
;
or newlines)
- for commands that output entire collections as a single object (which is rare), you want to count such collections as
1
object.[1]
- more generally, if you need to ensure that the command output is returned as a bona fide array, though note that it is invariably of type
[object[]]
; if you need a specific element type, use a cast (e.g., [int[]]
), but note that you then don't strictly need the @(...)
; e.g.,
[int[]] (...)
will do - unless you want to prevent enumeration of collections output as single objects.
Choose (...).Count
, if:
- only one command's output must be counted
- for commands that output entire collections as a single object, you want to count the individual elements of such collections; that is,
(...)
forces enumeration of command output.[2]
- for counting the elements of commands's output already stored in a variable - though, of course, you can then simply omit the
(...)
and use $var.Count
Caveat: Due to a longstanding bug (still present as of PowerShell Core 6.2.0), accessing .Count
on a scalar fails while Set-StrictMode -Version 2
or higher is in effect - use @(...)
in that case, but note that you may have to force enumeration.
To demonstrate the difference in behavior with respect to (rare) commands that output collections as single objects:
PS> @(Write-Output -NoEnumerate (1..10)).Count
1 # Array-as-single-object was counted as *1* object
PS> (Write-Output -NoEnumerate (1..10)).Count
10 # Elements were enumerated.
Performance considerations:
If a command's output is directly counted, (...)
and @(...)
perform about the same:
$arr = 1..1e6 # Create an array of 1 million integers.
{ (Write-Output $arr).Count }, { @(Write-Output $arr).Count } | ForEach-Object {
[pscustomobject] @{
Command = "$_".Trim()
Seconds = '{0:N3}' -f (Measure-Command $_).TotalSeconds
}
}
Sample output, from a single-core Windows 10 VM (the absolute timings aren't important, only that the numbers are virtually the same):
Command Seconds
------- -------
(Write-Output $arr).Count 0.352
@(Write-Output $arr).Count 0.365
By contrast, for large collections already stored in a variable, @(...)
introduces substantial overhead, because the collection is recreated as a (new) array (as noted, you can just $arr.Count
):
$arr = 1..1e6 # Create an array of 1 million integers.
{ ($arr).Count }, { @($arr).Count } | ForEach-Object {
[pscustomobject] @{
Command = "$_".Trim()
Seconds = '{0:N3}' -f (Measure-Command $_).TotalSeconds
}
}
Coding-style considerations:
The following applies in situations where @(...)
and (...)
are functionally equivalent (and either perform the same or when performance is secondary), i.e., when you're free to choose which construct to use.
Mathias recommends @(...).Count
, stating in a comment:
There's another reason to explicitly wrap it in this context - conveying intent, i.e., "We don't know if $p
is a scalar or not, hence this construct".
My vote is for (...).Count
:
Once you understand that PowerShell (v3 or higher) treats scalars as collections with count 1 on demand, you're free to take advantage of that knowledge without needing to reflect the distinction between a scalar and an array in the syntax:
When writing code, this means you needn't worry about whether a given command situationally may return a scalar rather than a collection (which is common in PowerShell, where capturing output from a command with a single output object captures that object as-is, whereas 2 or more output objects result in an array).
As a beneficial side effect, the code becomes more concise (and sometimes faster).
Example:
# Call Get-ChildItem twice, and, via Select-Object, limit the
# number of output objects to 1 and 2, respectively.
1..2 | ForEach-Object {
# * In the 1st iteration, $var becomes a *scalar* of type [System.IO.DirectoryInfo]
# * In the 2nd iteration, $var becomes an *array* with
# 2 elements of type [System.IO.DirectoryInfo]
$var = Get-ChildItem -Directory / | Select-Object -First $_
# Treat $var as a collection, which in PSv3+ works even
# if $var is a scalar:
[pscustomobject] @{
Count = $var.Count
FirstElement = $var[0]
DataType = $var.GetType().Name
}
}
The above yields:
Count FirstElement DataType
----- ------------ --------
1 /Applications DirectoryInfo
2 /Applications Object[]
That is, even the scalar object of type System.IO.DirectoryInfo
reported its .Count
sensibly as 1
and allowed access to "its first element" with [0]
.
For more about the unified handling of scalars and collections, see this answer.
[1] E.g., @(Write-Output -NoEnumerate 1, 2).Count
is 1
, because the Write-Output
command outputs a single object - the array 1, 2
- _as a whole. Because only a single object is output, @(...)
wraps that object in an array, resulting in , (1, 2)
, i.e. a single-element array whose first and only element is itself an array.
[2] E.g., (Write-Output -NoEnumerate 1, 2).Count
is 2
, because even though the Write-Output
command outputs the array as a single object, that array is used as-is. That is, the whole expression is equivalent to (1, 2).Count
. More generally, if a command inside (...)
outputs just one object, that object is used as-is; if it outputs multiple objects, they are collected in a regular PowerShell array (of type [object[]]
) - this is the same behavior you get when capturing command output via a variable assignment ($captured = ...
).