Repository: PowerShell/PowerShell
File: src/System.Management.Automation/engine/pipeline.cs (C#)

Code before:
            List<CommandProcessorBase> commands = _commands;
            if (commands is null)
            {
                return;
            }

            // Call StopProcessing() for all the commands.
            foreach (CommandProcessorBase commandProcessor in commands)
            {
                if (commandProcessor == null)
                {
                    throw PSTraceSource.NewInvalidOperationException();
                }

                try
                {
                    commandProcessor.Command.DoStopProcessing();
                }
                catch (Exception)
                {
                    // We swallow exceptions which occur during StopProcessing.
                    continue;
                }
            }

            _pipelineStopTokenSource.Cancel();
        }

        #endregion public_methods

        #region private_methods

        /// <summary>
        /// Partially execute the pipeline, and retrieve the output
        /// after the input objects have been entered into the pipe.
        /// </summary>
        /// <param name="input">
        /// Array of input objects for first stage
        /// </param>
        /// <returns>
        /// Results from last pipeline stage.  This will be empty if
        /// ExternalSuccessOutput is set.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// PipelineExecuteRequiresAtLeastOneCommand
        /// </exception>
        /// <exception cref="PipelineStoppedException">
        /// The pipeline has already been stopped, or a cmdlet encountered
        /// a terminating error
        /// </exception>
        /// <exception cref="ParameterBindingException">

Review comment (bug):
@jborean93 Sorry for my delay on getting back to this PR.

> + Should the token be cancelled before or after `StopProcessing` is called on each command in the pipeline
>   + Currently it will cancel it after

I prefer to moving it before the calls to `DoStopProcessing()`, so as to signal the cancellation sooner. With that, commands in pipeline that use the cancellation token could have started the cleanup while we are running `StopProcess` for those that don't.

> + Should the stop token be added the `ICommandRuntime` interface through a new `ICommandRuntime3` and that is used as the check
>   + Currently it just verifies the `CommandRuntime` of the `PSCmdlet` is `MshCommandRuntime`
>   + This should always be the case so it's a simple Debug assertion but maybe that will change in the future
>   + A new interface seems like a drastic change so I didn't go ahead with it

I agree that a new interface is overkill. But this property should be available to commands that derive from `Cmdlet` too. There are many cmdlets whose implementation actually derives from `Cmdlet`, such as `ConvertFrom-Json`, `Get/Wait/Stop/Debug-Process`, and quite some service cmdlets in PowerShell. When running in PowerShell, I believe we will assign an `MshCommandRuntime` instance to them. Also, `Cmdlet` has the public property `Stopping`, which make it reasonable for it to have the `PipelineStopToken` property as well.

Maybe we should consider adding the `PipelineStopToken` property to the abstract base class `InternalCommand` (`IsStopping` is defined in it, which is returned from `Cmdlet.Stopping`). Something like the following:
```c#
// in InternalCommand class

// internal bool IsStopping ...

internal CancellationToken StopToken => commandRuntime is MshCommandRuntime mcr
    ? mcr.PipelineProcessor.PipelineStopToken
    : CancellationToken.Default;
```
```c#
// in Cmdlet class

// public bool Stopping ...

public CancellationToken PipelineStopToken => StopToken;
```
@SeeminglyScience thoughts?

Code after:
            List<CommandProcessorBase> commands = _commands;
            if (commands is null)
            {
                return;
            }

            _pipelineStopTokenSource.Cancel();

            // Call StopProcessing() for all the commands.
            foreach (CommandProcessorBase commandProcessor in commands)
            {
                if (commandProcessor == null)
                {
                    throw PSTraceSource.NewInvalidOperationException();
                }

                try
                {
                    commandProcessor.Command.DoStopProcessing();
                }
                catch (Exception)
                {
                    // We swallow exceptions which occur during StopProcessing.
                    continue;
                }
            }
        }

        #endregion public_methods

        #region private_methods

        /// <summary>
        /// Partially execute the pipeline, and retrieve the output
        /// after the input objects have been entered into the pipe.
        /// </summary>
        /// <param name="input">
        /// Array of input objects for first stage
        /// </param>
        /// <returns>
        /// Results from last pipeline stage.  This will be empty if
        /// ExternalSuccessOutput is set.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// PipelineExecuteRequiresAtLeastOneCommand
        /// </exception>
        /// <exception cref="PipelineStoppedException">
        /// The pipeline has already been stopped, or a cmdlet encountered
        /// a terminating error
        /// </exception>
        /// <exception cref="ParameterBindingException">