PowerShell outputting array items when interpolating within double quotes

So when you are using interpolation, by default it interpolates just the next variable in toto. So when you do this:

"$test[0]"

It sees the $test as the next variable, it realizes that this is an array and that it has no good way to display an array, so it decides it can't interpolate and just displays the string as a string. The solution is to explicitly tell PowerShell where the bit to interpolate starts and where it stops:

"$($test[0])"

Note that this behavior is one of my main reasons for using formatted strings instead of relying on interpolation:

"{0}" -f $test[0]

In such cases you have to do:

echo "$($test[0])"

Another alternative is to use string formatting

echo "this is {0}" -f $test[0]

Note that this will be the case when you are accessing properties in strings as well. Like "$a.Foo" - should be written as "$($a.Foo)"


EBGreen's helpful answer contains effective solutions, but only a cursory explanation of PowerShell's string expansion (string interpolation):

  • Only variables by themselves can be embedded directly inside double-quoted strings ("...") (by contrast, single-quoted strings ('...'), as in many other languages, are for literal contents).

    • This applies to both regular variables and variables referencing a specific namespace; e.g.:
      "var contains: $var", "Path: $env:PATH"

    • If the first character after the variable name can be mistaken for part of the name - which notably includes : - use {...} around the variable name to disambiguate; e.g.:
      "${var}", "${env:PATH}"

    • To use a $ as a literal, you must escape it with `, PowerShell's escape character; e.g.:
      "Variable `$var"

  • Any character after the variable name - including [ and . is treated as a literal part of the string, so in order to index into embedded variables ($var[0]) or to access a property ($var.Count), you need $(...), the subexpression operator (in fact, $(...) allows you to embed entire statements); e.g.:

    • "1st element: $($var[0])"
    • "Element count: $($var.Count)"
    • "Today's date: $((Get-Date -DisplayHint Date | Out-String).Trim())"
  • Stringification (to-string conversion) is applied to any variable value / evaluation result that isn't already a string:

    • Caveat: Where culture-specific formatting can be applied, PowerShell chooses the invariant culture, which largely coincides with the US-English date and number formatting; that is, dates and numbers will be represented in US-like format (e.g., month-first date format and . as the decimal mark).
    • In essence, the .ToString() method is called on any resulting non-string object or collection (strictly speaking, it is .psobject.ToString(), which overrides .ToString() in some cases, notably for arrays / collections and PS custom objects)
      • Note that this is not the same representation you get when you output a variable or expression directly, and many types have no meaningful default string representations - they just return their full type name.
        However, you can embed $(... | Out-String) in order to explicitly apply PowerShell's default output formatting.

      • For a more comprehensive discussion of stringification, see this answer.


As stated, using -f, the string-formatting operator (<format-string> -f <arg>[, ...]) is an alternative to string interpolation that separates the literal parts of a string from the variable parts:

'1st element: {0}; count: {1:x}'  -f  $var[0], $var.Count
  • Note the use of '...' on the LHS, because the format string (the template) is itself a literal. Using '...' in this case is a good habit to form, both to signal the intent of using literal contents and for the ability to embed $ characters without escaping.

  • In addition to simple positional placeholders ({0} for the 1st argument. {1} for the 2nd, ...), you may optionally exercise more formatting control over the to-string conversion; in the example above, x requests a hex representation of the number.
    For available formats, see the documentation of the .NET framework's String.Format method, which the -f operator is based on.

  • Pitfall: -f has high precedence, so be sure to enclose RHS expressions other than simple index or property access in (...); e.g., '{0:N2}' -f 1/3 won't work as intended, only '{0:N2}' -f (1/3) will.

  • Caveats: There are important differences between string interpolation and -f:

    • Unlike expansion inside "...", the -f operator is culture-sensitive:

      • Therefore, the following two seemingly equivalent statements do not yield the same result:

             PS> [cultureinfo]::CurrentCulture='fr'; $n=1.2; "expanded: $n"; '-f: {0}' -f $n
        
             expanded: 1.2
             -f: 1,2
        
      • Note how only the -f-formatted command respected the French (fr) decimal mark (,).
        Again, see the previously linked answer for a comprehensive look at when PowerShell is and isn't culture-sensitive.

    • Unlike expansion inside "...", -f stringifies arrays as <type-name>[]:

         PS> $arr = 1, 2, 3; "`$arr: $arr"; '$arr: {0}' -f (, $arr)
      
          $arr: 1 2 3
          $arr: System.Object[]
      
      • Note how "..." interpolation created a space-separated list of the stringification of all array elements, whereas -f-formatting only printed the array's type name.
        (As discussed, $arr inside "..." is equivalent to:
        (1, 2, 3).psobject.ToString() and it is the generally invisible helper type [psobject] that provides the friendly representation.)

      • Also note how (, ...) was used to wrap array $arr in a helper array that ensures that -f sees the expression as a single operand; by default, the array's elements would be treated as individual operands.