Giuseppe Guerrini
Giuseppe Guerrini

Reputation: 4438

Powershell: unexpected echo in read-host

I am trying to write a simple text manipulation filter in powershell (5.1 on W10-64 bits). My goal is to use it as part of a pipeline in a batch (.bat), e.g.:

command1.exe | powershell "...my filter..." | command2.exe

I am facing a weird problem: "Read-Host" cmdlet always echoes its input. Example:

echo 1234 | powershell "$a=Read-Host"

produces

1234

(NOTE: see PowerShell: Disable echo in Read-Host).

How can I avoid this unexpected echo? Is there any alternate way to read STDIN in powershell?

NOTE2: I know I could do all the pipeline work inside powershell, but for many reason this doesn't fit my case. What I want is a "generic filter" suitable to be part of a pipeline in a ".bat". And, no, a temporary file to store intermediate data is not an option.

EDIT: as noted by "Compo", MS documentation says this about Read_Host:

You cannot pipe input to this cmdlet

So there is no point in trying to make it work.

EDIT2: As explainded by "mklement0", "[Read_Host] does accept pipeline, i.e. stdin input from the outside, such as when provided to PowerShell's CLI". The real problem is that it is intended to simplify user input, not to give generic access to stdin.

The actual question is: Is there any alternate way to read STDIN in powershell?

Upvotes: 1

Views: 796

Answers (2)

mklement0
mklement0

Reputation: 439852

This answer complements Mathias R. Jessen's helpful answer, which shows the right way to handle data passed to the PowerShell CLI via the pipeline (stdin).


Read-Host:

  • Does not accept pipeline input from inside a PowerShell session, which is what the documentation refers to.

  • Does accept pipeline, i.e. stdin[1] input from the outside, such as when provided to PowerShell's CLI.

However, this comes with limitations:

  • As in interactive use, Read-Host only ever reads one line.

  • Because interactive behavior is emulated even with stdin input, the input is invariably echoed to the display as well, which cannot be suppressed.

  • Unlike all other cmdlets (as well as expressions) - which require the explicit use of $input in order to process stdin data, as shown in Mathias' answer - Read-Host implicitly reads stdin input.

    • The implication is that you must either consume stdin input entirely via Read-Host calls or use $input to relay it to other commands.

This leaves only one legitimate use case for piping strings to Read-Host from outside PowerShell:

  • Providing automated responses to a given command's / script's interactive prompts; e.g.:

    C:\>echo y | powershell -c "$response = Read-Host 'Continue (y/n)'; if ($response.Trim() -ne 'y') { exit 2 }; 'continuing...'"
    Continue (y/n): y
    continuing...
    
  • See this answer for more information.


Conversely, this means that you shouldn't use Read-Host to collect stdin input in a variable, given that only one line is read and given the unwanted echoing behavior.

Typically, you simply relay stdin input by command-internally piping $input to other commands, as shown in Mathias' answer; to explicitly collect all stdin input in a variable up front, you can apply $(), the subexpression operator (or @(), the array-subexpression operator) to the $input enumerator:

C:\>(echo abc & echo def) | powershell "$allInput=$($input); \"$($allInput.Count) lines received.\""
2 lines received.

[1] stdin, the standard input stream, is an OS-level concept, as are the standard output streams, stdout and stderr. By contrast, PowerShell implements its own system of streams: the 6(!) output streams are described in the conceptual about_Redirection topic; the success output stream (number 1) and the error stream (2) are the analogs to stdout and stderr, respectively. However, there is no stdin analog as such: (stream) input to other commands is only ever provided via the pipeline (and not also via the < operator supported in other shells, which PowerShell does not support).

Upvotes: 3

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174835

Is there any alternate way to read STDIN in powershell?

That's already happening, you just need to know how to hook it - like using the $input automatic enumerator variable for example:

C:\> echo "camelCase" |powershell -Command "$input |ForEach-Object ToUpper"
CAMELCASE

Since PowerShell and CMD use slightly different syntaxes and can be a bit of a pain to escape, I'd strongly recommend putting your filter(s) in script files and invoking those:

# myUpperCaseFilter.ps1
$input |ForEach-Object ToUpper
# script.bat
command1.exe |powershell -File path\to\myUpperCaseFilter.ps1 |command2.exe

Upvotes: 5

Related Questions