Skip to content

Allow early termination of process block #27389

Closed

Description

@x5i4k4

Summary of the new feature / enhancement

Given following function to get frist item matching the given pattern:

function first {
    param (
        [Parameter(ValueFromPipeline)]
        [psobject]$InputObject,

        [Parameter(Position = 1, Mandatory)]
        [scriptblock]$Selector
    )
    process {
        if ($Selector.InvokeWithContext($null, [psvariable]::new('_', $_))) {
            $_
            # I need termination here when it found first match
        }
    }
}

1..10 | first { $_ % 2 -eq 0 } # expected: 2

I would expect it terminate the process when it found a match, we have following possible workarounds:

  • return: aka continute for process but it will keep spinning for rest of the reduandant items
  • break: not sure about its semantic, it can early break the process but has the following problem:
function first {
    param (
        [Parameter(ValueFromPipeline)]
        [psobject]$InputObject,

        [Parameter(Position = 1)]
        [psobject]$Selector = 1
    )
    process {
        if ($Selector.InvokeWithContext($null, [psvariable]::new('_', $_))) {
            $_
            break
        }
    }
}

1..10 | first { $_ % 2 -eq 0 } | select -f 1 # 2
1..10 | first { $_ % 2 -eq 0 } | measure # none

For certain cmdlet like measure, it can't receive the items from first using break? While select works fine. Should it be considered as bug?
Given another function collect that collect everything from pipeline and return in end block, also reproduces the problem.
So looks like break not only breaks the process, it also skip the end block evaluation?

function collect {
    begin {
        $foo = [System.Collections.Generic.List[object]]@()
    }
    process {
        $foo.Add($_)
    }
    end {
        $foo
    }
}

1..10 | first { $_ % 2 -eq 0 } | collect # none

Proposed technical implementation details (optional)

Adding new keyword can be unnecessary, simply add a new method $PSCmlet.Break() or something should be sufficient.
Or just make break to eval end block as well(potentially breaking change), if I understand the problem correctly.

Activity

  1. added
    Issue-Enhancementthe issue is more of a feature request than a bug
    Needs-TriageThe issue is new and needs to be triaged by a work group.
    on May 3, 2026
  2. iRon7 commented on May 3, 2026

    @iRon7

    Duplicate with #3821?

  3. jhoneill commented on May 3, 2026

    @jhoneill

    Duplicate with #3821?

    I think this nearly the same but there's an end block difference.

    > 1..1000 | foreach -Begin {write-host "starting"} -Process {start-sleep $_;  $_} -end {write-host 'Done'} | select -First 1
    starting
    1
    >
    

    Normally people ask for "Can I stop an expensive process earlier in the pipeline, in the same the way that select-object does to avoid a long wait here.

    That runs cleanly but it stops the end block running. This is "Stop" process and still run the end-block. Not sure how well that works when the end block can send objects to the pipeline (e.g. sort-object) the main purpose of stop is to prevent objects being sent.

  4. iRon7 commented on May 4, 2026

    @iRon7

    The way I see it, for where the propose possibly deviates from #3821: you could simply append | Select-Object -First 1 to the selection filter knowing that Select-Object by itself actually affects the upstream commands and where the end blocks downstream aren't affected:

    1..10 | Where-Object { $_ % 2 -eq 0 } | Select-Object -First 1 | Measure-Object # or: ... | Collect
  5. TobiasPSP commented on May 20, 2026

    @TobiasPSP
    Collaborator

    I would suggest we implement it with the existing architecture in mind:

    function Stop-Pipeline
    {
      $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet)
      $scriptCmd = {& $wrappedCmd -First 1 }
      $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
      $steppablePipeline.Begin($true)
      $steppablePipeline.Process(1)
      $steppablePipeline.End()
      $steppablePipeline.Dispose()
    }
    
    "Start"
    1..10  | ForEach-Object {  if ($_ -eq 5) { Stop-Pipeline }; $_ }
    "End"

    So in essence, use the existing mechanisms implemented for features like Select-Object -First 1 (throwing a special exception - PipelineStoppedException - that travels upstream and terminates all upstream cmdlets gracefully).

    Meanwhile, you can use the Stop-Pipeline implementation from above as a workaround.

  6. added and removed
    Needs-TriageThe issue is new and needs to be triaged by a work group.
    on May 20, 2026
  7. SteveL-MSFT commented on May 20, 2026

    @SteveL-MSFT
    Member

    Dupe of #3821

  8. x5i4k4 commented on May 21, 2026

    @x5i4k4
    Author

    Dr. Tobias Weltner (@TobiasPSP) well it indeed resolves the special case of break in ForEach-Object, but it still skips end block evaluation which is the feature I actually want

    1..10  | ForEach-Object {  if ($_ -eq 5) { Stop-Pipeline }; $_ } | Measure-Object # none
  9. x5i4k4 commented on May 21, 2026

    @x5i4k4
    Author

    I think my issue is more about whether end block should be executed on break, while #3821 is more about the propagation of the termination, I know end is also mentioned on that propasal, I just don't see people discussing much about end there.

  10. TobiasPSP commented on May 21, 2026

    @TobiasPSP
    Collaborator

    Got it. So you want the end block to behave like „finally“ in „try“? Interestingly, I assumed it would execute „end“ as part of gracefully breaking.

  11. microsoft-github-policy-service commented on May 22, 2026

    @microsoft-github-policy-service
    Contributor

    This issue has been marked as duplicate and has not had any activity for 1 day. It has been closed for housekeeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Enhancementthe issue is more of a feature request than a bugResolution-DuplicateThe issue is a duplicate.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions