Cmdlets themselves typically use no collection type in their output.[1]:
They emit individual objects to the pipeline, which can situationally mean: zero, one, or multiple ones.
This is precisely what Get-ADUser
does: the specific number of output objects depends on the arguments that were given; that is why the Get-AdUser
help topic only mentions scalar type ADUser
as the output type and states that it "returns one or more" of them.
Generally, the PowerShell pipeline is meant to be a stream of objects whose particular length (object count) need not be known in advance, with commands in subsequent pipeline segments processing the previous segment's output objects one by one, as they're being received (see about_Pipelines).
However, it is the PowerShell engine that automatically collects multiple outputs for you in an [object[]]
array[2] if needed, notably if you capture output via a variable assignment or use a command call via (...)
, the grouping operator (or $(...)
, the subexpression operator[3]), as an expression:
# Get-ChildItem C:Windows has *multiple* outputs, so PowerShell
# collects them in an [object[]] array.
PS> $var = Get-ChildItem C:Windows; $var.GetType().Name
Object[]
# Ditto with (...) (and also with $(...) and always with @(...))
PS> (Get-ChildItem C:Windows).GetType().Name
Object[]
However, if a given command - possibly situationally - only outputs a single object, you'll then get just that object itself - it is not wrapped in an array:
# Get-Item C: (always) returns just 1 object.
PS> $var = Get-Item C:; $var.GetType().Name
DirectoryInfo # *not* a single-element array,
# just the System.IO.DirectoryInfo instance itself
What can get tricky is that a given command can situationally produce either one or multiple outputs, depending on inputs and runtime conditions, so the engine may return either a single object or an array.
# !! What $var receives depends on the count of subdirs. in $HOMEProjects:
PS> $var = Get-ChildItem -Directory $HOMEDocuments; $var.GetType().Name
??? # If there are *2 or more* subdirs: an Object[] array of DirectoryInfo instances.
# If there is only *one* subdir.: a DirectoryInfo instance itself.
# (See below for the case when there is *no* output.)
@()
, the array-subexpression operator, is designed to eliminate this ambiguity, if needed:
By wrapping a command in @(...)
, PowerShell ensures that its output is always collected as [object[]]
- even if the command happens to produce just one output object or even none:
PS> $var = @(Get-ChildItem -Directory $HOMEProjects); $var.GetType().Name
Object[] # Thanks to @(), the output is now *always* an [object[]] array.
With variable assignments, a potentially more efficient alternative is to use an [array]
type constraint to ensure that the output becomes an array:
# Alternative to @(...)
# Note: You may also create a strongly typed array, with on-demand type conversions:
# [string[]] $var = ...
PS> [array] $var = Get-ChildItem -Directory $HOMEDocuments; $var.GetType().Name
Object[]
Note:
This is potentially more efficient in that if the RHS already happens to be an array, it is assigned as-is, whereas @(...)
actually enumerates the output from ...
and then reassembles the elements into a new ([object[]]
) array.
[array]
preserves the specific type of an input array by simply passing it through (e.g., [array] $var = [int[]] (1..3)
stores the [int[]]
array as-is in $var
).
Placing the [array]
"cast" to the left of $var = ...
- which is what it makes it a type constraint on the variable - means that the type of the variable is locked in, and assigning different values to $var
later will continue to convert the RHS value to [array]
([object[]]
), if needed (unless you assign $null
or "nothing" (see below)).
Taking a step back: Ensuring that collected output is an array is often not necessary, due to PowerShell's unified handling of scalars and collections in v3+:
If a command produces no output, you'll get "nothing" (strictly speaking: the [System.Management.Automation.Internal.AutomationNull]::Value
singleton), which in most cases behaves like $null
[4]:
# Get-Item nomatchingfiles* produces *no* output.
PS> $null -eq (Get-Item nomatchingfiles*)
True
# Conveniently, PowerShell lets you call .Count on this value, which the
# behaves like an empty collection and indicates 0.
PS> (Get-Item nomatchingfiles*).Count
0
[1] It is possible to output entire collections as a whole to the pipeline (in PowerShell code with Write-Output -NoEnumerate $collection
or, more succinctly, , $collection
), but that is then just another object in the pipeline that happens to be a collection itself. Outputting collections as a whole is an anomaly, however, that changes how commands you pipe to see the output, which can be unexpected; a prominent example is ConvertFrom-Json
s unexpected behavior prior to v7.0.
[2] a System.Array
instance whose elements are of type System.Object
, allowing you to mix objects of different types in a single array.
[3] Use of (...)
is usually sufficient; $(...)
is only needed for string interpolation (expandable strings) and for embedding whole statements or multiple commands in a larger expression; note that $(...)
, unlike (...)
by itself, unwraps single-element arrays; compare (, 1).GetType().Name
to $(, 1).GetType().Name
; see this answer.
[4] There are scenarios in which "nothing" behaves differently from $null
, notably in the pipeline and in switch
statements, as detailed in this comment on GitHub; the linked issue is a feature request to make "nothing" more easily distinguishable from $null
, by supporting -is [AutomationNull]
as
a test.