Search code examples
powershelltypesreflection

Win32 Executable Output Parsing with PowerShell


my $PSVersionTable output is as follows:

Name                           Value
----                           -----
PSVersion                      7.0.1
PSEdition                      Core
GitCommitId                    7.0.1
OS                             Microsoft Windows 10.0.19041
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

If I enter

wsl -l | Get-Member

the output informs me that the output type is a String.

If I enter

(wsl -l).GetType()

the output informs me that the output type is Array.

The entire reason I'm looking at this is that I was trying to parse the output of that command and for a long, frustrating time, I was thinking that I was working with a single, contiguous string with embedded carriage return / line feeds, but it appears I'm actually working with an array of strings.

So my question: what do the parentheses around the command do to seemingly change the nature of the output of the executable? Is it that without the parentheses the output from 'wsl -l' is being streamed to Get-Member one line (array element) at a time?

Thank you!


Solution

  • If you want to use Get-Member to inspect an object as-is, you mustn't use the pipeline, because the pipeline auto-enumerates arrays (enumerables), and Get-Member then reports the (distinct set of) types among their elements.

    Instead, use Get-Member -InputObject, which reports on arrays (enumerables) as a whole:

    Get-Member -InputObject (wsl -l) # -> shows the members of type System.Object[]
    

    PowerShell streams the stdout output from external programs such as wsl, line by line. That is, it sends each line as it is being received to the pipeline.

    If you assign the command's output to a variable or enclose the command in (), the grouping operator, the stream output is collected:

    • If there's only one output line, a (single) [string] instance is returned.

    • If there are multiple output lines, PowerShell collects them in a regular, [object[]]-typed array for you, whose elements are [string] instances in this case.

    If you want to avoid this ambiguity and ensure that the result is always an array, you can use @(), the array-subexpression operator - @(wsl -l) - or, on assigning to a variable, type-constrain that variable as an array - [array] $out = wsl -l


    To get a single, potentially multi-line string representation of the output, use the -join operator:

    # See note re character encoding below.
    (wsl -l) -join "`n"   # or use [Environment]::NewLine instead of "`n"
    

    [Environment]::NewLine returns the platform-appropriate newline char. / sequence, i.e. LF-only ("`n") on Unix-like platforms, CRLF ("`r`n") on Windows. However, PowerShell happily accepts either form on all platforms.

    Note:

    • wsl.exe meta commands such as -l, i.e. commands that provide information about or relate to managing distributions (as opposed to interacting with a given distribution) by default output "Unicode", i.e. UTF-16LE-encoded strings; in order for PowerShell to interpret them correctly, additional work is needed - see this answer. In the simplest case, make sure that $env:WSL_UTF8 = 1 is in effect.

    • If you're using PowerShell (Core) 7, you can alternatively use the Join-String cmdlet:

      wsl -l | Join-String -Separator "`n"
      
    • While the Out-String cmdlet would return a single, multi-line string too, it invariably adds a trailing newline (and if you were to use -NoNewLine, it wouldn't use any newlines); this surprising behavior is the subject of GitHub issue #14444.