无法让 ValueFromPipeline 在 Powershell 模块中工作

Can't get ValueFromPipeline to work in Powershell Module

我正在创建一个 Powershell 模块,作为与 RESTful API 接口的包装器。

为了简化脚本编写者的工作,我提供此 Cmdlet 以“建立连接”到 API(实质上是创建 Worker class 对象并指定身份验证令牌):

public class GetAPIConnection : Cmdlet
{

    [Parameter(Position=0, Mandatory = true, ValueFromPipeline = true)]
    [Alias("T","Password","Pwd","APIKey")]
    public string Key { get; set; }

    //This is the object I intend to send to the pipeline
    private APIWorker API;

    protected override void BeginProcessing()
    {
        base.BeginProcessing();
        BuildOutputObject();
    }

    protected override void ProcessRecord()
    {
        base.ProcessRecord();
        WriteObject(API);
    }

    private BuildOutputObject()
    {
        API = new APIWorker(APIKey);
    }

我遇到的问题是我希望其他 cmdlet 能够从管道中接受由 Get-APIConnection 生成的对象。这是我如何尝试在另一个 Cmdlet 中实现它的示例:

[Cmdlet(VerbsCommon.Get, @"SecurityGroups")]
[OutputType(typeof(string))]
public class GetSecurityGroups : Cmdlet
{
    private APIWorker connection;

    [Parameter(Position = 0,
               Mandatory = true,
               ValueFromPipeline = true)]
    public APIWorker Connection
    {
        get { return connection; }
        set 
        {
            //Confirmation that set is actually being called
            Console.WriteLine(@"Got here!");

            connection = value; 
        }
    }

    [Parameter(Mandatory = true)]
    public string Identity { get; set; }

    private IEnumerable<string> Result;

    protected override void BeginProcessing()
    {
        base.BeginProcessing();
        BuildOutputObject();
    }

    protected override void ProcessRecord()
    {
        base.ProcessRecord();
        WriteObject(Result);
    }

    private BuildOutputObject()
    {
        Result = Connection.GetUserGroups(Identity);
    }

因此,在导入模块后,我可以使用 Get-APIConnection cmdlet 创建 PoshAPI 对象:

>$API = Get-APIConnection -Key '[My Token]'

...如果我将 $Connection 作为命名参数传递,Get-SecurityGroups 会起作用:

>Get-SecurityGroups -Connection $API -Identity 'abcdefg'
Got here!
PowershellUsers
DotNetCOE
CompanyCarOwners 
...

注意“到了这里!”被发送到屏幕,表明 set 确实在“连接”参数

上被调用

...但是,即使我在“Get-SecurityGroups”中为“Connection”参数指定了 ValueFromPipeline 属性,我也无法传递 $API 由于某种原因从管道中删除:

$API | Get-SecurityGroups -Identity 'abcdefg'

Get-SecurityGroups : Object reference not set to an instance of an object.
At line:1 char:7
+ $API | Get-SecurityGroups -Identity 'abcdefg'
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-SecurityGroups], NullReferenceException
    + FullyQualifiedErrorId : System.NullReferenceException,APIHelper.GetSecurityGroups

异常详细信息表明在 BuildOutputObject() 中抛出了空引用异常:

> $error[0] | select *


PSMessageDetails      :
Exception             : System.NullReferenceException: Object reference not set to an instance of an object.
                           at APIHelper.GetSecurityGroups.BuildOutputObject()
                           at System.Management.Automation.Cmdlet.DoBeginProcessing()
                           at System.Management.Automation.CommandProcessorBase.DoBegin()
TargetObject          :
CategoryInfo          : NotSpecified: (:) [Get-SecurityGroups], NullReferenceException
FullyQualifiedErrorId : System.NullReferenceException,APIHelper.GetSecurityGroups
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}

有趣的是,我能够访问 BuildOutputObject(),因为 Connection 是一个强制参数,并且没有调用它(因此没有“Got here!”)。提示我定义该参数不是正确的行为吗??

如有任何帮助,我们将不胜感激!

在 PowerShell 管道中,参数绑定是在进程块中完成的,而不是在开始块中。如果您正在构建 PowerShell commandlet,我认为相同的规则将适用。

看看这个例子,它和你的相似GetSecurityGroups

function Test-Pipeline {
    
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,ValueFromPipeline)]
        [string]$a
    )

    BEGIN {Write-Verbose "begin $a"}
    PROCESS {Write-Verbose "process $a"}
    END {Write-Verbose 'end'}

}

在没有管道的情况下调用它,给出您期望的输出:

PS C:\> Test-Pipeline 'abc' -Verbose
VERBOSE: begin abc
VERBOSE: process abc
VERBOSE: end

但是,用管道参数调用它有小的区别。看到了吗?

PS C:\> 'abc' | Test-Pipeline -Verbose
VERBOSE: begin 
VERBOSE: process abc
VERBOSE: end

是的,在开始块 $a 没有值!在本例中,$a 是简单类型,因此没有错误。在你的例子中,你调用了 null 对象的方法,所以你得到了错误。

原因很简单。在这个例子中是可见的。

PS C:\> 'abc','def' | Test-Pipeline -Verbose
VERBOSE: begin 
VERBOSE: process abc
VERBOSE: process def
VERBOSE: end

管道执行甚至在管道中每个 PowerShell 函数中的执行开始块处理第一个值之前就开始了。

P.S。如果您尝试这样做,我想您也会得到意想不到的结果:

'[My Token]' | Get-APIConnection

简单来说,我的问题是我需要将我的 BuildOutputObject() 方法移动到 ProcessRecord() 中。 BeginProcessing() 正在执行时尚未读取管道。所以,在 ProcessRecord() 方法中设置输出对象的值

[Cmdlet(VerbsCommon.Get, @"SecurityGroups")]
[OutputType(typeof(string))]
public class GetSecurityGroups : Cmdlet
{
    private APIWorker connection;

    [Parameter(Position = 0,
               Mandatory = true,
               ValueFromPipeline = true)]
    public APIWorker Connection
    {
        get { return connection; }
        set 
        {
            //Confirmation that set is actually being called
            Console.WriteLine(@"Got here!");

            connection = value; 
        }
    }

    [Parameter(Mandatory = true)]
    public string Identity { get; set; }

    private IEnumerable<string> Result;

    protected override void BeginProcessing()
    {
        base.BeginProcessing();
    }

    protected override void ProcessRecord()
    {
        base.ProcessRecord();
        BuildOutputObject();
        WriteObject(Result);
    }

    private BuildOutputObject()
    {
        Result = Connection.GetUserGroups(Identity);
    }