Liturgist Liturgist - 2 months ago 27x
Powershell Question

Getting stdin into the Powershell stream

The following script works well when the filename is specified on the command line.


@echo off
set "COUNT=%1"
set "COUNT=%COUNT:-=%"
set "FILENAME=%~2"
powershell "Get-Content %FILENAME% -Last %COUNT%"

However, what I need is to be able to pipe the text into
from stdin. I would like to write the following to get the last three Subversion tags assigned to the project. What can I do to get the source to
to be stdin?

svn ls svn://ahost/arepo/aproject/tags | call tail.bat -3

NB: I am not permitted to install any helpful tools like
from the outside. It has to be done with the programs already available on the machine.


Rewrite tail.bat as follows:

@echo off

set "COUNT=%1"
set "COUNT=%COUNT:-=%"
set "FILENAME=%~2"

if "%FILENAME%"=="" (
  powershell -noprofile -command "$Input | Select-Object -Last %COUNT%"
) else (
  powershell -noprofile -command "Get-Content \"%FILENAME%\" -Last %COUNT%"

This will make PowerShell read stdin input via $Input, if no filename argument was passed, courtesy of this answer.


C:> (echo one & echo two & echo three) | tail.bat -2


  • While PowerShell generally sends through the pipeline and outputs objects of any kind, its interface to the outside world invariably involves strings.

  • Thus, given that $Input is an enumerator that represents outside stdin input, we can be sure that it enumerates the input text lines (as strings) one by one, so all we need is to select the lines of interest, which is why piping to Select-Object is sufficient.

  • By contrast, reading a file by name in PowerShell requires Get-Content (which, incidentally, also sends the input file's lines one by one through the pipeline, unless you also specify -Raw); since Get-Content has tail functionality built in, via parameter -Tail (and its alias -Last), it is all that is needed here.

  • CAVEAT: Character decoding on input and re-encoding on output is involved when PowerShell talks to the outside world:

    • If you're only ever dealing with ASCII-encoded input (single-byte characters with code points ranging between 0 - 127), you needn't worry.

    • Otherwise, prepare for a world of pain - see below for details.

Decoding/Re-Encoding Issues

  • Assuming that PowerShell recognizes your input encoding (see below), the output encoding is invariably what the console window's assigned encoding is; by default, unfortunately, that is the OEM codepage (e.g., the "DOS" code page CP437 on US-English systems), reflected in PS as [Console]::OutputEncoding.

    • Thus, with properly recognized input, if you print to the console, things will look OK, but if you capture the output in a file, you'll end up with an OEM-codepage-encoded file, which is probably undesired.

    • If feasible, you could fundamentally set up your console windows to use your codepage (input and output encoding) of choice (using chcp), but trying to change the encoding ad-hoc in your script is, unfortunately, not an option.
      Note that using UTF-8 - codepage 65001 - only works if you configure your console windows to use one of the TT (TrueType) fonts.

  • As written above, the set of input encodings that are properly recognized is unfortunately limited to the following, based on the default input encoding (which is also the OEM codepage, reflected in PS as [Console]::InputEncoding; remember: input will be re-encoded on output):

    • ASCII input (re-encoding on output will by default preserve this encoding)
    • UTF-16 LE input with a BOM (which is what PowerShell calls Unicode, subject to re-encoding to something potentially different on output)
  • You could hard-code an expected input encoding by adding -Encoding <enc> to the Get-Content call (which expects the Windows default codepage encoding by default), but to do the same for stdin input (as reflected in $Input) would be non-trivial.

    • E.g., with the default input encoding, if you explicitly wanted to interpret the input as UTF-8 (again, note that on output [Console]::OutputEncoding encoding is applied):
      powershell -noprofile -command "$Input | % { [text.encoding]::utf8.GetString([Console]::InputEncoding.GetBytes($_)) } | Select-Object -Last %COUNT%"