Skip to content

Powershell commands run to completion with a broken pipe to an external program #15329

@stinos

Description

@stinos

Steps to reproduce

Run

Get-ChildItem -Recurse -Force | more

then exit more by pressing q.

Expected behavior

The pipe stops after quitting more.

Actual behavior

The pipe keeps on running until all child items listed.

Environment data

PSVersion                      7.1.3
PSEdition                      Core

Description

There are a couple of issues related to unix-like plaforms and pipe handling (#8420 #8421) but the same behavior seems to appears on any platform with any type of external program.

Is this a known/documented issue? I looked around but maybe my search terms are not ok, I couldn't find a related issue.

For example: these keep running the upstream command even after the downstream command closes; (the cmd /c '@echo off' command is just to mimick an external program which consumes the pipe then stops without output before all items are consumed - a real example would be a program like fzf):

ls -Rec -Fo -File | more
ls -Rec -Fo -File | cmd /c '@echo off'

this can be worked around with Select-Object to force a StopUpstreamCommandsException, e.g. this works as expected in that the pipe stops:

ls -Rec -Fo -File | & "C:\Program Files\Git\usr\bin\head.exe" | Select-Object -First 10

however that's only ok because it is known head will by default produce 10 items. That's a problem because it means piping into a command which produces no output will not stop the pipe when that command exits. So this still keeps on running:

ls -Rec -Fo -File | cmd /c '@echo off' | Select-Object -First 1 | Out-Null

One workaround is be to launch the external program with the .net Process API, then use Register-ObjectEvent -EventName 'Exited' and then throw a StopUpstreamCommandsException using a technique like https://stackoverflow.com/a/34800670. But that's non-trivial.

Another simpler one would be to make the external command always produce output no matter what. I hoped pipeline chain operators could do that but due to associativity that doesn't cut it. That is to say, I assume that

ls -Rec -Fo -File | cmd /c '@echo off' || 'TERMINATOR' | Select-Object -First 1 | Out-Null

doesn't stop right away because it comes down to

(ls -Rec -Fo -File | cmd /c '@echo off') || 'TERMINATOR' | Select-Object -First 1 | Out-Null

and not

ls -Rec -Fo -File | (cmd /c '@echo off' || 'TERMINATOR') | Select-Object -First 1 | Out-Null

(which yields a ParserError).

The idea to always have the external command produce output can be done in cmd like

ls -Rec -Fo -File | cmd /c '(@echo off || echo TERMINATOR) && echo TERMINATOR' | Select-Object -First 1 | Out-Null

i.e. no matter if the external program (as said mimicked by @echo off) produces output or not, the echos after it make sure it does. Is there another workaround?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions