Why does Range.BorderAround emit “True” to the console?

Why is this needed?

It is needed, because the BorderAround method has a return value and, in PowerShell, any command or expression ... that outputs (returns) data is implicitly output to the (success) output stream, which by default goes to the host, which is typically the console window (terminal) in which a PowerShell session runs.

That is, the data shows in the console/terminal, unless it is:

  • captured ($var = ...)
  • sent through the pipeline for further processing (... | ...; the last pipeline segment’s command may or may not produce output itself)
  • redirected (... >)

or any combination thereof.

That is:

$range.BorderAround(1, -4138)

is (more efficient) shorthand for:

Write-Output $range.BorderAround(1, -4138)

(Explicit use of Write-Output is rarely needed.)

Since you don’t want that output, you must suppress it, for which you have several options:

  • $null = ...

  • [void] (...)

  • ... > $null

  • ... | Out-Null

$null = ... may be the best overall choice, because:

  • It conveys the intent to suppress up front

    • While [void] = (...) does that too, it often requires you to enclose an expression in (...) for syntactic reasons; e.g., [void] 1 + 2 doesn’t work as intended, only [void] (1 + 2); similarly, a command must always be enclosed in (...); [void] New-Item test.txt doesn’t work, only [void] (New-Item test.txt) does.
  • It performs well with both command output (e.g., $null = Get-AdUser ...) and expression output (e.g., $null = $range.BorderAround(1, -4138)).

Conversely, avoid ... | Out-Null, because it is generally much slower (except in the edge case of a side effect-free expression’s output in PowerShell (Core) 6+)[1].

However, if you need to silence all output streams – not just the success output, but also errors, verbose output, … – you must use *> $null


Why does PowerShell produce output implicitly?

  • As a shell, PowerShell’s output behavior is based on streams, as in traditional shells such as cmd.exe or Bash. (While traditional shells have 2 output streams – stdout and stderr – PowerShell has 6, so as to provide more sophisticated functionality – see about_Redirection.)

    • A cmdlet, script, or function can write to the output streams as often as it wants, and such output is usually instantly available for display but notably also to potential consumers, which enables the streaming, one-by-one processing that the pipeline provides.

    • This contrasts with traditional programming languages, whose output behavior is based on return values, typically provided via the return keyword, which conflates output data (the return value) with flow control (exit the scope and return to the caller).

      • A frequent pitfall is to expect PowerShell’s return statement to act the same, but it doesn’t: return <val> is just syntactic sugar for <val>; return, i.e., implicit output of <val> followed by an unconditional return of control to the caller; notably, the use of return does not preclude generation of output from earlier statements in the same scope.
  • Unlike traditional shells, PowerShell doesn’t require an explicit write-to-the-output stream command in order to produce output:

    • While PowerShell does have a counterpart to echo, namely Write-Output, its use is rarely needed.

      • Among the rare cases where Write-Output is useful is preventing enumeration of a collection on output with -NoEnumerate, or to use common parameter -OutVariable to both output data and capture it in a variable (which is generally only needed for expressions, because cmdlets and advanced functions / scripts themselves support -OutVariable).
    • The implicit output behavior:

      • is generally a blessing:

        • for interactive experimentation – just type any statement – notably including expressions such as [IO.Path]::GetExtension('foo.txt') and [math]::Pow(2, 32) – and see its output (akin to the behavior of a REPL).
        • for writing concise code that doesn’t need to spell out implied behavior (see example below).
      • can occasionally be a pitfall:

        • for users accustomed to the semantics of traditional programming languages.

        • due to the potential for accidental pollution of the output stream from statements that one doesn’t expect to produce output, such as in your case; a more typical example is the .Add() method of the [System.Collections.ArrayList] class unexpectedly producing output.

Example:

# Define a function that takes an array of integers and
# outputs their hex representation (e.g., '0xa' for decimal 10)
function Get-HexNumber {
  param([int[]] $numbers)      
  foreach ($i in $numbers) {
    # Format the integer at hand 
    # *and implicitly output it*.
    '0x{0}' -f $i.ToString('x')
  }
}

# Call the function with integers 0 to 16 and loop over the
# results, sleeping 1 second between numbers.
Get-HexNumber (0..16) | ForEach-Object { "[$_]"; Start-Sleep 1 }

The above yields the following:

[0x0]
# 1-second pause
[0x1]
# 1-second pause
[0x2]
...
[0x10]

This demonstrates the streaming aspect of the behavior: Get-HexNumber‘s output is available to the ForEach-Object cmdlet call as it is being produced, not after Get-HexNumber has exited.


[1] In PowerShell (Core) 6+, Out-Null has an optimization if the only preceding pipeline segment is a side effect-free expression rather than a method or command call; e.g., 1..1e6 | Out-Null executes in almost no time, because the expression is seemingly not even executed. However, such a scenario is atypical, and the functionally equivalent Write-Output (1..1e6) | Out-Null takes a long time to run, much longer than $null = Write-Output (1..1e6).

Leave a Comment