1

I have the following working call when I execute it directly in my PowerShell window:

$myexe = "C:\MyExe.exe"
"MyString" | & $myexe // works
Write-Output "MyString" | & $myexe // seems to work too

However, when I perform the same in a function of a PowerShell script it does no longer work. The program does not receive the string...any ideas?

mklement0
  • 382,024
  • 64
  • 607
  • 775
D.R.
  • 20,268
  • 21
  • 102
  • 205
  • "when I perform the same in a function of a PowerShell script it does no longer work." - can you show us such a script? – Mathias R. Jessen Feb 17 '21 at 16:28
  • ditto, because as noted below, with just the posted lines in a pristine .ps1, it works as expected, as shown below. – postanote Feb 17 '21 at 16:35
  • It's very hard to describe: we realized yesterday that due to the complexity of the script, we overlooked that it executed a different version of the same executable + the maintainers of the executable explicitly removed the feature of piping in data to this executable between those two versions. So that's the actual reason why it doesn't work and why we couldn't provide a suitable example. – D.R. Feb 20 '21 at 08:51
  • @D.R. It sounds like you figured out the answer to your question. It's probably worth writing that up quickly and accepting it so this question can be marked closed -- and, if you're able to provide more detail, help someone in the same situation down the line. Though even accepting a dupe of your comment is probably helpful. – ruffin Nov 19 '21 at 22:50

2 Answers2

2

Unlike POSIX-compatible shells such as bash, PowerShell does not automatically relay pipeline input received by a given script or function to commands you call from inside that script or function.

To do so, you must use the automatic $input variable[1]:

For instance:

function foo {
  # Relay the pipeline input received by this function 
  # to an external program, using `cat` as an example.
  $input | & /bin/cat -n # append $args to pass options through
}

'MyString' | foo

Note that & is optional in this case, because /bin/cat is an unquoted, literal path - see this answer for when & is required.

The output (on a Unix-like platform) is: 1 MyString, showing that the cat utility received the foo function's own pipeline input via its stdin, thanks to use of $input.

Without piping $input to cat, the latter would receive no stdin input at all (and in the case of cat would block, waiting for interactive input).

If you want to support passing filename arguments too - in lieu of pipeline input - more work is needed:

function foo {
  if ($MyInvocation.ExpectingInput) { # Pipeline input present
    # Relay the pipeline input received by this function 
    # to an external program, using `cat` as an example.
    $input | /bin/cat -n $args
  } else { # NO pipeline input.
    /bin/cat -n $args
  }
}

'MyString' | foo  # pipeline input
foo file.txt      # filename argument

Note:

  • Only non-advanced functions and scripts can make use of the $input variable, and its use implies that all input piped to the enclosing function/script is collected in full, up front, before sending it on begins.

  • To truly stream a script / function's own pipeline input to a single invocation of an external program - i.e. to relay the input as it is being received by the script / function - requires direct use of .NET APIs, namely System.Diagnostics.ProcessStartInfo and System.Diagnostics.Process.


[1] Similarly, $input is required to access data piped to a PowerShell command from outside PowerShell, via PowerShell's CLI; e.g., from bash:
echo hi | pwsh -c '$input | ForEach-Object { "[{0}]" -f $_ }'

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • would you happen to have a good example of note number 2, "...requires direct use of .NET APIs"? – Gregor y Feb 19 '21 at 23:54
  • @Gregory, I haven't looked, but I assume that there are C# examples out there. If you have trouble adapting them to PowerShell, I encourage you to ask a new question (feel free to post a link to it here to notify me, once you have done so). – mklement0 Feb 20 '21 at 00:01
  • this was one of the avenues I was trying to chase down here https://stackoverflow.com/a/66270719/4496560 as a third solution to either have a `[cmdlet]`/`[pscmdlet]` object as a `NoteProperty` or to return one back to the pipeline via a `ScriptMethod`, but it just wasn't working out for me – Gregor y Feb 20 '21 at 00:11
1

With a simple script that only contains this...

$myexe = 'nslookup.exe'
'stackexchange.com' | & $myexe # works
Write-Output 'stackexchange.com' | & $myexe # seems to work too

..., it works as expected in the Consolehost/WindowsTerminal as expected.

 .\TestExePipe.ps1
# Results
<#
Default Server:  ...
Address:  ...

> Server:  ...
Address:  ...

Non-authoritative answer:
Name:    stackexchange.com
Addresses:  151.101.193.69
...

> Default Server:  ...
Address:  ...

> Server:  ...
Address:  ...

Non-authoritative answer:
Name:    stackexchange.com
Addresses:  151.101.129.69
          ...

>
#>
postanote
  • 15,138
  • 2
  • 14
  • 25