These rules explain the strangest PowerShell bugs: arrays that become scalars and return values with unexpected extra items.
Everything uncaptured is output. return $x is just "output $x and leave"; any earlier expression that produced a value, such as a .NET method returning an index (ArrayList.Add) or a cmdlet you forgot to silence, is output too, so the caller receives an array. Discard with $null = ..., [void](...) or | Out-Null (the first two are fastest).
Unrolling: when a collection is written to the pipeline, it is enumerated item by item. A function returning a one-element array therefore gives the caller a scalar, and an empty array gives nothing ($null). To return the collection itself, prefix it with the unary comma: return , $list. On the caller side, @(...) reliably turns any result into an array.
PowerShell has six streams: 1 Success (output), 2 Error, 3 Warning, 4 Verbose, 5 Debug, 6 Information. Write-Host writes to the Information stream since 5.0, which is why it can now be captured. Redirect by number: 2>&1 merges errors into output, 3> warnings.txt saves warnings, *> $null silences everything, *>&1 merges all streams into output.
$null comparisons also involve this: an empty pipeline result is a special "AutomationNull" that behaves as $null but, unlike $null, produces no iteration in foreach.
Example
function Get-Two { 'noise'; return 'value' } # BOTH are output
(Get-Two).Count # 2
function Get-One { return @(42) }
(Get-One).GetType().Name # Int32 (unrolled to a scalar)
function Get-Wrapped { return , @(42) } # unary comma keeps the array
(Get-Wrapped).GetType().Name # Object[]
@(Get-One).Count # 1 (@() forces an array)
function Get-None { return @() }
$null -eq (Get-None) # True
$al = [System.Collections.ArrayList]::new()
function Add-Item { $al.Add('x'); 'done' } # Add returns an index...
Add-Item # 0 / done (leaked output)
$null = $al.Add('y') # discard it like this
function Get-Info { Write-Warning 'careful'; Write-Information 'fyi'; 'data' }
$out = Get-Info 3>&1 6>&1 # merge warning + information
$out.Count # 3
Get-Info *> $null # silence every streamRemember
- Every uncaptured value is output; return only exits
- Collections unroll: return , $arr to keep one, @() to force an array
- Streams 1-6; redirect with 2>&1, 3>, *> $null
Watch out for
- Calling methods like ArrayList.Add() in a function without discarding the result, so the caller gets extra numbers.