首页 > 解决方案 > PowerShell Cmdlet 开发:关于在 Cmdlet 之间通过管道传输的 IEnumerable 的最佳实践

问题描述

假设您有两个 Cmdlet:第一个返回输出,第二个使用该输出作为输入参数。

public class Cmdlet1 : PSCmdlet
{
    protected override void ProcessRecord()
    {
        List<string> result = ...
        WriteObject(result, true);
    }
}

public class Cmdlet2 : PSCmdlet
{
    [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)]
    public string Input { get; set; }

    protected override void ProcessRecord()
    {
        // Do a long operation on Input.
    }
}

Cmdlet2 将以这种方式被调用多次(对于 Cmdlet1 返回的 IEnumerable 中的每个元素一次),您可以很好地将它们通过管道连接在一起:选择对象-前 10 | Cmdlet2 只有 Cmdlet1 输出的前 10 个元素被发送到 Cmdlet2。

因为 Cmdlet2 需要一些时间,所以显示进度会很好。目前我们不能这样做,因为我们只能一一接收输入字符串。我们把 Cmdlet2 的输入改成 List

public class Cmdlet1 : PSCmdlet
{
    protected override void ProcessRecord()
    {
        List<string> result = ...
        WriteObject(result, true);
    }
}

public class Cmdlet2 : PSCmdlet
{
    [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)]
    public List<string> Input { get; set; }

    protected override void ProcessRecord()
    {
        // Do a long operation on Input.
    }
}

我们仍然一一获取输入字符串,因为 Cmdlet1 中的 WriteObject 调用具有 true 作为第二个参数,允许 PowerShell 将结果枚举到单个对象。如果我们将其更改为 false,PowerShell 不会枚举结果,并且 Cmdlet2 会获取整个字符串列表作为输入,我们可以对所有字符串进行操作并显示一个漂亮的进度条。

public class Cmdlet1 : PSCmdlet
{
    protected override void ProcessRecord()
    {
        List<string> result = ...
        WriteObject(result, false);
    }
}

public class Cmdlet2 : PSCmdlet
{
    [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)]
    public List<string> Input { get; set; }

    protected override void ProcessRecord()
    {
        // Do a long operation on Input, displaying a progress bar.
    }
}

但是:现在我们不能再轻易地管它们了。Cmdlet1 | 选择对象-前 10 | Cmdlet2 现在将整个输入发送到 Cmdlet2!Select-Object 不采用前 10 个,因为只有 1 个元素是 IEnumerable,并且该元素完全发送到 Cmdlet2!

处理这种情况的正确方法是什么?

标签: powershellpipecmdletpscmdlet

解决方案


将长时间运行的操作移至EndProcessing块,用于ProcessRecord聚合输入:

public class Cmdlet2 : PSCmdlet
{
    [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)]
    public string[] InputObject { get; set; }

    private List<string> inputList = new List<string>();

    protected override void ProcessRecord()
    {
        inputList.AddRange(InputObject);
    }

    protected override void EndProcessing()
    {
        for(int i = 0; i < inputList.Count; i++)
        {
            this.WriteProgress(new ProgressRecord(1, "Doing long running operation", $"Currently processing item {i+1} / {inputList.Count}"));
            DoLongRunningOperation(inputList[i]);
        }
    }
}

推荐阅读