You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让编译后的Cmdlet即时输出脚本块生成的每个对象?

如何让编译后的Cmdlet在调用脚本块时即时输出每个对象?

原生Invoke-Command的即时输出表现

使用原生Invoke-Command时,脚本块输出的对象会被即时传递到下游管道:

Invoke-Command {
    'a'; Write-Verbose 'a' -Verbose
    'b'; Write-Verbose 'b' -Verbose
} |
. { process { Write-Verbose "downstream_$_" -Verbose } }

输出结果:

VERBOSE: downstream_a
VERBOSE: a
VERBOSE: downstream_b
VERBOSE: b

可以看到,脚本块每输出一个对象,下游的process块就会立即被调用处理。


自定义Cmdlet的延迟输出问题

如果用自定义编译的Cmdlet替换Invoke-Command,默认实现会先收集脚本块的所有输出,直到脚本执行完毕才一次性输出:

Add-Type       `
    -PassThru  `
    -TypeDefinition @'
using System.Management.Automation;
using System.Collections.Generic;

namespace n {
    [Cmdlet(VerbsLifecycle.Invoke,"SomeScriptBlockThing")]
    public class InvokeSomeScriptBlockThing : Cmdlet
    {
        [Parameter(Mandatory=true,Position=1)]
        public ScriptBlock ScriptBlock { get; set; }
        protected override void EndProcessing() {
            var output = ScriptBlock.Invoke("some_fancy_argument");
            foreach (var o in output) {
                WriteObject(o);
            }
        }
    }
}
'@             |
    % Assembly |
    Import-Module

Invoke-SomeScriptBlockThing {
    param($SomeFancyArgument)
    'a'; Write-Verbose 'a' -Verbose
    'b'; Write-Verbose 'b' -Verbose
} |
    . { process { Write-Verbose "downstream_$_" -Verbose } }

输出结果:

VERBOSE: a
VERBOSE: b
VERBOSE: downstream_a
VERBOSE: downstream_b

所有对象被累积后一次性输出,下游管道只能在脚本块执行完成后才开始处理。


问题背景

实际需求是要向脚本块传递CancellationToken,要求Cmdlet的生命周期长于该token,因此需要Cmdlet直接调用脚本块并传递参数,但当前实现的延迟输出不符合要求。旧方案中通过启动独立PowerShell实例的方式会破坏父作用域的访问权限,不可取。


解决方案:实时捕获脚本块输出

核心是不要使用ScriptBlock.Invoke()收集全部结果,而是用PSDataCollection实时捕获脚本块的输出,并在数据新增时立即调用WriteObject输出到管道。修改后的Cmdlet代码如下:

Add-Type       `
    -PassThru  `
    -TypeDefinition @'
using System.Management.Automation;
using System.Collections.Generic;

namespace n {
    [Cmdlet(VerbsLifecycle.Invoke,"SomeScriptBlockThing")]
    public class InvokeSomeScriptBlockThing : Cmdlet
    {
        [Parameter(Mandatory=true,Position=1)]
        public ScriptBlock ScriptBlock { get; set; }
        protected override void EndProcessing() {
            // 创建实时输出收集集合
            using var outputCollection = new PSDataCollection<object>();
            
            // 订阅数据新增事件,即时输出对象
            outputCollection.DataAdded += (sender, e) => {
                var collection = (PSDataCollection<object>)sender;
                // 处理从当前索引开始的新增数据
                for (int i = e.Index; i < collection.Count; i++) {
                    WriteObject(collection[i]);
                }
            };

            // 准备要传递给脚本块的参数变量
            var variables = new List<PSVariable> {
                new PSVariable("SomeFancyArgument", "some_fancy_argument")
            };

            // 使用InvokeWithContext执行脚本块,将输出定向到实时集合
            ScriptBlock.InvokeWithContext(null, variables, outputCollection);

            // 标记输出集合完成,确保所有数据都被处理
            outputCollection.Complete();
        }
    }
}
'@             |
    % Assembly |
    Import-Module

修改后的效果

再次运行测试代码,输出会和原生Invoke-Command一致:

VERBOSE: downstream_a
VERBOSE: a
VERBOSE: downstream_b
VERBOSE: b

脚本块每生成一个对象,就会被即时传递到下游管道处理,同时保留了对父作用域的访问权限,也能正常传递CancellationToken等参数。


内容的提问来源于stack exchange,提问作者alx9r

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:05:54