DynamicParameter可以依赖其他DynamicParameters的值吗?

时间:2015-04-07 08:37:14

标签: c# powershell cmdlets

我在cmdlet GetDynamicParameters()上有一个Get-DateSlain,它可以执行以下操作:

    public object GetDynamicParameters()
    {
        List<string> houseList = {"Stark", "Lannister", "Tully"};

        var attributes = new Collection<Attribute>
            {
                new ParameterAttribute
                {
                    HelpMessage = "Enter a house name",
                },
                new ValidateSetAttribute(houseList.ToArray()),
            };

        if (!this.ContainsKey("House"))
        {
            this.runtimeParameters.Add("House", new RuntimeDefinedParameter("House", typeof(string), attributes));
        }
    }

这可以按预期工作 - 用户可以键入Get-DateSlain -House,并在可用的房屋中进行制表。但是,一旦选择了房屋,我希望能够将结果缩小到该房屋中的角色。此外,如果它的房子是Stark&#39;,我想允许-Wolf参数。所以要实现(为简洁起见,删除了一些值有效性检查):

    public object GetDynamicParameters()
    {
        if (this.runtimeParameters.ContainsKey("House"))
        {
            // We already have this key - no need to re-add. However, now we can add other parameters
            var house = this.runtimeParameters["House"].Value.ToString();
            if (house == "Stark")
            {
                List<string> characters = { "Ned", "Arya", "Rob" };
                var attributes = new Collection<Attribute>
                {
                    new ParameterAttribute
                    {
                        HelpMessage = "Enter a character name",
                    },
                    new ValidateSetAttribute(characters.ToArray()),
                };

                this.runtimeParameters.Add("Character", new RuntimeDefinedParameter("Character", typeof(string), attributes));

                List<string> wolves = { "Shaggydog", "Snow", "Lady" };
                var attributes = new Collection<Attribute>
                {
                    new ParameterAttribute
                    {
                        HelpMessage = "Enter a wolf name",
                    },
                    new ValidateSetAttribute(wolves.ToArray()),
                };

                this.runtimeParameters.Add("Wolf", new RuntimeDefinedParameter("Wolf", typeof(string), attributes));
            }
            else if (house == "Lannister")
            {
                List<string> characters = { "Jaimie", "Cersei", "Tywin" };
                // ...
            }
            // ...

            return this.runtimeParameters;
        }

        List<string> houseList = {"Stark", "Lannister", "Tully"};

        var attributes = new Collection<Attribute>
        {
            new ParameterAttribute
            {
                HelpMessage = "Enter a house name",
            },
            new ValidateSetAttribute(houseList.ToArray()),
        };

        this.runtimeParameters.Add("House", new         RuntimeDefinedParameter("House", typeof(string), attributes));
    }

这看起来应该有用,但它没有。 GetDynamicParameters函数仅调用一次,即在向this.runtimeParameters["House"]提供值之前。由于在填写该值后没有重新评估,因此永远不会添加其他字段,ProcessRecord中依赖于这些字段的任何逻辑都将失败。

那么 - 有没有办法让多个动态参数相互依赖?

1 个答案:

答案 0 :(得分:2)

看一下这个问题的答案,它显示了一种访问GetDynamicParameters方法中其他动态参数值的方法: Powershell module: Dynamic mandatory hierarchical parameters

我调整了上述答案中的代码,以便它可以处理SwitchParameters,并将原始输入参数转换为cmdlet参数的实际类型。如果要获取值的动态参数是通过管道传递的,则它不起作用。我认为这是不可能的,因为在评估管道输入之前总是创建动态参数。这是:

public static class DynamicParameterExtension
{
    public static T GetUnboundValue<T>(this PSCmdlet cmdlet, string paramName, int unnamedPosition = -1))
    {
        var context = TryGetProperty(cmdlet, "Context");
        var processor = TryGetProperty(context, "CurrentCommandProcessor");
        var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
        var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;

        if (args != null)
        {
            var isSwitch = typeof(SwitchParameter) == typeof(T);

            var currentParameterName = string.Empty;
            object unnamedValue = null;
            var i = 0;
            foreach (var arg in args)
            {
                var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
                if (isParameterName != null && true.Equals(isParameterName))
                {
                    var parameterName = TryGetProperty(arg, "ParameterName") as string;
                    currentParameterName = parameterName;
                    if (isSwitch && string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
                    {
                        return (T)(object)new SwitchParameter(true);
                    }
                    continue;
                }

                var parameterValue = TryGetProperty(arg, "ArgumentValue");

                if (currentParameterName != string.Empty)
                {
                    if (string.Equals(currentParameterName, paramName, StringComparison.OrdinalIgnoreCase))
                    {
                        return ConvertParameter<T>(parameterValue);
                    }
                }
                else if (i++ == unnamedPosition)
                {
                    unnamedValue = parameterValue;
                }

                currentParameterName = string.Empty;
            }

            if (unnamedValue != null)
            {
                return ConvertParameter<T>(unnamedValue);
            }
        }

        return default(T);
    }

    static T ConvertParameter<T>(this object value)
    {
        if (value == null || Equals(value, default(T)))
        {
            return default(T);
        }

        var psObject = value as PSObject;
        if (psObject != null)
        {
            return psObject.BaseObject.ConvertParameter<T>();
        }

        if (value is T)
        {
            return (T)value;
        }
        var constructorInfo = typeof(T).GetConstructor(new[] { value.GetType() });
        if (constructorInfo != null)
        {
            return (T)constructorInfo.Invoke(new[] { value });
        }

        try
        {
            return (T)Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            return default(T);
        }
    }

    static object TryGetProperty(object instance, string fieldName)
    {
        if (instance == null || string.IsNullOrEmpty(fieldName))
        {
            return null;
        }

        const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
        var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);

        try
        {
            if (propertyInfo != null)
            {
                return propertyInfo.GetValue(instance, null);
            }
            var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);

            return fieldInfo?.GetValue(instance);
        }
        catch (Exception)
        {
            return null;
        }
    }        
}

因此,对于您的示例,您应该能够像:

一样使用它
public object GetDynamicParameters()
{
    var houseList = new List<string> { "Stark", "Lannister", "Tully" };

    var attributes = new Collection<Attribute>
    {
        new ParameterAttribute { HelpMessage = "Enter a house name" },
        new ValidateSetAttribute(houseList.ToArray()),
    };
    var runtimeParameters = new RuntimeDefinedParameterDictionary
    {
        {"House", new RuntimeDefinedParameter("House", typeof (string), attributes)}
    };

    var selectedHouse = this.GetUnboundValue<string>("House");

    //... add parameters dependant on value of selectedHouse

    return runtimeParameters;
}

毕竟我不确定尝试获取这些动态参数值是否一个好主意。 PowerShell Cmdlet API显然不支持它(参见GetUnboundValue方法中访问私有成员的所有反射),你必须重新实现PowerShell参数转换魔法(参见ConvertParameter,我确定我错过了一些案例)和流水线值有限制。使用风险由您自行承担:))