manojlds
manojlds

Reputation: 301177

Stop execution of cmdlet in ProcessRecord

How does one stop the execution of a cmdlet that takes in pipeline input in the ProcessRecord method. If a condition in ProcessRecord is not met, I need to immediately stop execution and return a false:

protected override void BeginProcessing()
{
    _output = true;
}

protected override void ProcessRecord()
{
    //processing
    if(condtion == true) return;

    _output = false;
    //How do I stop processing now, and ensure _output is returned as result?
}

protected override void EndProcessing()
{
    WriteObject(_output);
}

PipelineStoppedException seems to work, but doesn't give me the exact behaviour.

Updated with a more concrete example, using PipelineStoppedException:

Let's consider a cmdlet First-List which is supposed to behave similar to First() in LINQ. An implementation of this cmdlet would be like:

[Cmdlet("First", "List")]
public class FirstList : Cmdlet
{
    [Parameter(Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
    public object Input { get; set; }

    [Parameter(Position = 0, Mandatory = true)]
    public ScriptBlock ScriptBlock { get; set; }

    protected override void ProcessRecord()
    {
        var output = ScriptBlock.InvokeWithContext(null, new List<PSVariable>
        {
            new PSVariable("input", Input),
        })[0];

        if (output.ToString() != "True") return;

        WriteObject(Input);
        throw new PipelineStoppedException();
    }

    protected override void EndProcessing()
    {
        Error.NoMatch();
    }
}

With Select -First, I am able to do the following:

$a = 1..10 | Select -First 1
#$a is 1

But with my implementtion:

$a = 1..10 | First-List { $input -ge 5 }
#$a should be assigned 5, but it is not

But 1..10 | First-List { $input -ge 5 } does indeed output 5.

Update 2:

It seems like Select-Object actually throws StopUpstreamCommandsException.

There is also a feedback to have this available here - https://connect.microsoft.com/PowerShell/feedback/details/768650/enable-users-to-stop-pipeline-making-stopupstreamcommandsexception-public

Upvotes: 2

Views: 1212

Answers (3)

manojlds
manojlds

Reputation: 301177

I got the behaviour of Select -First by throwing the StopUpstreamCommandsException. But since it was internal to System.Management.Automation, had to use reflection. Wrote a utility method that looks like below:

internal static class Error
{
    private static readonly Type StopUpstreamCommandsExceptionType =
        Assembly.GetAssembly(typeof (PSCmdlet))
            .GetType("System.Management.Automation.StopUpstreamCommandsException");

    internal static Exception StopUpstreamCommandsException(Cmdlet cmdlet)
    {
        var stopUpstreamCommandsException = (Exception) Activator.CreateInstance(StopUpstreamCommandsExceptionType,
            BindingFlags.Default | BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public,
            null,
            new Object[] {(InternalCommand) cmdlet},
            null
            );
        return stopUpstreamCommandsException;
    }
}

Upvotes: 4

beefarino
beefarino

Reputation: 1151

Many of the built-in cmdlets throw a System.Management.Automation.PipelineStoppedException when they want to halt the running pipeline. E.g., that's how select-object -first N works. Have you tried that?

Upvotes: 1

Keith Hill
Keith Hill

Reputation: 201692

Not at my dev machine at the moment. Does it work if you throw a System.Management.Automation.PipelineStoppedException?

Upvotes: 1

Related Questions