Repository navigation
Description
The ability to stop a pipeline on demand is currently only available internally, as used by Select-Object -First <n>, for instance.
To quote from this uservoice.com issue:
It is of great value to be able to stop the pipeline if your mission is completed before the emitting cmdlet has provided all results.
In fact, the PS pipeline would gain a completely new "feature". Currently, it is a memory throttle and provides realtime feedback at the cost of performance. If stopping the pipeline was something available to users, it could also be a generic means of running cmdlets only partially.
Select-Objectis a great start but you do not always know beforehand how many elements you need. So it would be beneficial to have aStop-Pipelinecmdlet or at least a publicStopUpstreamCommandsExceptionthat a script programme can throw.
Also note that there are two distinct ways to "stop" a pipeline:
- Exiting the entire pipeline instantly (effectively aborting it) - with downstream cmdlets not getting a chance to run their
Endblocks, as simulated by this command usingbreakwith a dummy loop to break out of:
# Produces NO output, because Sort-Object's End block never runs.
do {
1..10 | % { if ($_ -gt 2) { break }; $_ } | Sort-Object -Descending
} while ($False)Note that without the final Sort-Object -Descending pipeline stage, you would see output, because the objects are being output in the % script block as they're being received.
This quiet termination of the entire pipeline is similar to a statement-terminating error that is silenced.
- Stopping only the upstream cmdlets (the cmdlet calls in earlier pipeline stages), as
Select-Object -First <n>does, for instance, giving downstream cmdlets a chance to run theirEndblocks.
# Produces (2, 1), because Sort-Object's End block IS run.
1..10 | Select-Object -First 2 | Sort-Object -DescendingThis scenario is about stopping further input, while still allowing remaining pipeline stages to finish their processing.
However, the fact that the upstream cmdlets do not also get to run their End blocks can be problematic, as that may be required for cleanup tasks: see #7930; PR #9900 would instead introduce a cleanup block to ensure cleanup even when stopped via StopUpstreamCommandsException.
Originally reported for:
PowerShell Core v6.0.0-beta (v6.0.0-beta.1)
Activity
dragonwolf83 commentedon May 20, 2017
There is a good detailed blog post on how to implement this in PowerShell today: Cancelling a Pipeline. I think it would be useful to implement something like as a built-in cmdlet.
mklement0 commentedon May 20, 2017
Thanks, Troy Campbell (@dragonwolf83); the blog post's author, Tobias Weltner, is the same person who created the linked uservoice.com issue; the blog post elicited the following Jeffrey Snover (@jpsnover) response (dated "over 7 years ago"):
That's one lofty bar! :)
SteveL-MSFT commentedon May 20, 2017
Things that don't necessarily align with the PowerShell Team's priorities (which is not static) can still get done if contributed by the community :)
iSazonov commentedon May 20, 2017
We need RFC for new cmdlet and examples of how to use the cmdlet.
[-]Allow user code to stop a pipeline on demand[/-][+]Allow user code to stop a pipeline on demand / to terminate upstream cmdlets.[/+]alx9r commentedon Jan 1, 2018
Does anyone have a working link to Tobias' blog post mentioned above? That link seems to be dead.
TobiasPSP commentedon Jan 2, 2018
Here‘s some material: http://www.powertheshell.com/stop_pipeline/
SeeminglyScience commentedon Jun 20, 2022
Adding
Needs-TriagesoWG-Enginecan discuss it. This comes up surprisingly often. Typically for profile functions, but still. The lengths one has to go through to get the same behavior is pretty wild (and unsupported).My proposal:
namespace System.Management.Automation { public abstract class PSCmdlet { + [DoesNotReturn] + public void StopUpstreamCommands(); } }If added this should be tested both for binary cmdlets (
this.StopUpstreamCommands()) and script cmdlets ($PSCmdlet.StopUpstreamCommands())2 remaining items
SeeminglyScience commentedon Apr 21, 2025
The Engine WG discussed this today, we agree this would be a useful feature. We should also ensure that the exception thrown by the method is documented so folks know to rethrow it when surrounding with a
try. Also we should ensure that it works properly when thrown from the first pipeline position.TobiasPSP commentedon May 20, 2026
Here is a prototype taking advantage of the existing behavior in
Select-Object:Just make sure
Stop-Pipelinecannot be called outside a pipeline, or else you get the same behavior as withbreak: it stops your entire script. The exception raised internally bySelect-Objectmust be caught inside a pipeline and not be allowed to escape into the script scope.SeeminglyScience commentedon May 20, 2026
There's a few caveats to that though. Unfortunately I don't remember most of them, but an example is if you switch from
ForEach-Objectto& { process {it starts acting more likebreak. If you addCmdletBindingit'll work again.There are other scenarios where that occurs but I can never remember them, it's come up before as a possible solution.
In order to get it to work in all scenarios, this is what I do in my profile (though in case it needs to be said, this is very outside of support).
TobiasPSP commentedon May 21, 2026
Thank you for your comment. Your code is raising the magic exception directly whereas the proposed solution from above indirectly does the same, just by using Select-Object as proxy.
Both ways work reliably. The reason why Select-Object failed in simple functions and worked in advanced functions is probably due to the way how proxy functions work. Occasionally users repurposed autogenerated sample code which may require $PSCmdlet (NULL in simple functions). The suggested solution above does not have this limitation.
That said, at this time the exception you raise directly is marked non-public, and although unlikely, it may change at any time without notice. So at this point, strictly speaking, only the Select-Object-approach is safe to use.
However, this approach is also a bit overkill. I would propose to properly document the exception and make it public, then either use this alone (raising the exception does the trick and is a one-liner), or add a simple wrapper cmdlet or keyword for discoverability.
While at this, maybe someone can look into the break keyword and (a) fix its runaway tendency when used outside loops that crosses even module boundaries and leads to very hard-to-identify cross-module issues currently, and then (b) add the pipeline break functionality to this existing keyword, based on the exception, again including proper safe-guarding (as this exception will also runaway when issued outside a pipeline).
Making „break“ also work properly in pipelines seems the most intuitive solution even though potential breaking changes would require scrutiny here.
SeeminglyScience commentedon May 21, 2026
Yeah so I don't remember the specifics, but the proxy does not always work. It's come up a few times in the discord as a potential solution until folks hit the blocker sometime after. If I can find it I'll post it here, but for whatever reason I always forget the exact issue that crops up.
Correct! Though both workarounds arguably rely on undocumented implementation detail. I don't suggest the use of either in production code.
Yeah, there are a few proposals out there that need to be discussed. I'm partial to adding a
PSCmdlet.StopUpstreamCommands()method personally.That one, unfortunately, is by design behavior that is depended on by folks. For example:
I would have made it a parse error personally, but it is what it is.
powercode commentedon Oct 3, 2026
In addition to
PSCmdlet.StopUpstreamCommands(), I think we should add a predicate,IsPowerShellControlFlowException(Exception e).The idea is that users can then write the following:
Combine that with a Roslyn analyzer, and we have something that could really help users.