c# - Powershell 模块 : Dynamic mandatory hierarchical parameters

标签 c# powershell

所以我真正想要的是在 PS 模块中有点可用的制表符补全。 ValidateSet 似乎是去这里的方式。

不幸的是我的数据是动态的,所以我不能预先用所有有效值注释参数。 DynamicParameters/IDynamicParameters 似乎是那个问题的解决方案。

将这些东西放在一起(并将我的失败归结为一个简单的测试用例)我们最终得到:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace PSDummy
{
    [Cmdlet(VerbsCommon.Get, "BookDetails")]
    public class GetBookDetails : Cmdlet, IDynamicParameters
    {
        IDictionary<string, string[]> m_dummyData = new Dictionary<string, string[]> {
            {"Terry Pratchett", new [] {"Small Gods", "Mort", "Eric"}},
            {"Douglas Adams", new [] {"Hitchhiker's Guide", "The Meaning of Liff"}}
        };

        private RuntimeDefinedParameter m_authorParameter;
        private RuntimeDefinedParameter m_bookParameter;

        protected override void ProcessRecord()
        {
              // Do stuff here..
        }

        public object GetDynamicParameters()
        {
            var parameters = new RuntimeDefinedParameterDictionary();

            m_authorParameter = CreateAuthorParameter();
            m_bookParameter = CreateBookParameter();

            parameters.Add(m_authorParameter.Name, m_authorParameter);
            parameters.Add(m_bookParameter.Name, m_bookParameter);
            return parameters;
        }

        private RuntimeDefinedParameter CreateAuthorParameter()
        {
            var p = new RuntimeDefinedParameter(
                "Author",
                typeof(string),
                new Collection<Attribute>
                {
                    new ParameterAttribute {
                        ParameterSetName = "BookStuff",
                        Position = 0,
                        Mandatory = true
                    },
                    new ValidateSetAttribute(m_dummyData.Keys.ToArray()),
                    new ValidateNotNullOrEmptyAttribute()
                });

            // Actually this is always mandatory, but sometimes I can fall back to a default
            // value. How? p.Value = mydefault?

            return p;
        }

        private RuntimeDefinedParameter CreateBookParameter()
        {
            // How to define a ValidateSet based on the parameter value for
            // author?
            var p = new RuntimeDefinedParameter(
                "Book",
                typeof(string),
                new Collection<Attribute>
                {
                    new ParameterAttribute {
                        ParameterSetName = "BookStuff",
                        Position = 1,
                        Mandatory = true
                    },
                    new ValidateSetAttribute(new string[1] { string.Empty }/* cannot fill this, because I cannot access the author */),
                    new ValidateNotNullOrEmptyAttribute()
                });

            return p;
        }
    }
}

不幸的是,这个小片段已经引起了很多问题。降序排列:

  • 我看不出如何在参数之间建立联系。如果你选择一个作者,你应该只能选择与作者匹配的书。到目前为止GetDynamicParameters()尽管似乎总是无状态的:我看不出有什么办法可以访问不同/较早的动态参数的值。尝试将其保存在一个字段中,尝试搜索 MyInvocation - 没运气。这可能吗?

  • 如何为强制参数定义默认值?不适合这个愚蠢的例子,但假设你可以存储你最喜欢的作者。从现在开始,我想默认为那个作者,但仍然必须有一个指向作者的指针。要么你给了我一个默认值(并且仍然可以指定其他东西),要么你需要明确。

  • 带空格的字符串的制表符补全看起来很奇怪/损坏/有限 - 因为它没有用引号将值括起来(例如,如果您键入 dir C:\Program <tab>,就会像 cmd.exe 那样)。所以 Tab 完成实际上 中断 调用(如果上述问题得到解决,Get-BookDetails Ter<tab> 将/将扩展为 Get-BookDetails Terry Pratchett,它将姓氏放在参数位置 1 中,也就是“book”。

不应该这么难,肯定有人已经做过类似的事情了吧?

更新:经过又一天的修修补补和鬼混,我看不出有什么方法可以让这项工作成功。 commandlet 是无状态的,将被一遍又一遍地实例化。在我可以定义动态参数 (GetDynamicParameters) 的时间点,我无法访问它们的(当前)值/查看它们将绑定(bind)到什么 - 例如MyInvocation.BoundParameters 为零。我会让这个问题悬而未决,但似乎这只是不受支持。我看到的所有示例都添加了一个基于静态参数值的动态参数——这与此处无关。 SCSS 。

最佳答案

我认为这行得通。不幸的是,它使用反射来为您的第一个项目符号获取一些 cmdlet 的私有(private)成员。我的想法来自 Garrett Serack .我不确定我是否完全理解如何设置默认作者,所以我这样做是为了将最后一个有效作者存储在静态字段中,这样您下次就不需要 -Author 了。

代码如下:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;

namespace PSDummy
{
    internal class DynParamQuotedString {
        /*
            This works around the PowerShell bug where ValidateSet values aren't quoted when necessary, and
            adding the quotes breaks it. Example:

            ValidateSet valid values = 'Test string'  (The quotes are part of the string)

            PowerShell parameter binding would interperet that as [Test string] (no single quotes), which wouldn't match
            the valid value (which has the quotes). If you make the parameter a DynParamQuotedString, though,
            the parameter binder will coerce [Test string] into an instance of DynParamQuotedString, and the binder will
            call ToString() on the object, which will add the quotes back in.
        */

        internal static string DefaultQuoteCharacter = "'";

        public DynParamQuotedString(string quotedString) : this(quotedString, DefaultQuoteCharacter) {}
        public DynParamQuotedString(string quotedString, string quoteCharacter) {
            OriginalString = quotedString;
            _quoteCharacter = quoteCharacter;
        }

        public string OriginalString { get; set; }
        string _quoteCharacter;

        public override string ToString() {
            // I'm sure this is missing some other characters that need to be escaped. Feel free to add more:
            if (System.Text.RegularExpressions.Regex.IsMatch(OriginalString, @"\s|\(|\)|""|'")) {
                return string.Format("{1}{0}{1}", OriginalString.Replace(_quoteCharacter, string.Format("{0}{0}", _quoteCharacter)), _quoteCharacter);
            }
            else {
                return OriginalString;
            }
        }

        public static string[] GetQuotedStrings(IEnumerable<string> values) {
            var returnList = new List<string>();
            foreach (string currentValue in values) {
                returnList.Add((new DynParamQuotedString(currentValue)).ToString());
            }
            return returnList.ToArray();
        }
    }


    [Cmdlet(VerbsCommon.Get, "BookDetails")]
    public class GetBookDetails : PSCmdlet, IDynamicParameters
    {
        IDictionary<string, string[]> m_dummyData = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) {
            {"Terry Pratchett", new [] {"Small Gods", "Mort", "Eric"}},
            {"Douglas Adams", new [] {"Hitchhiker's Guide", "The Meaning of Liff"}},
            {"An 'Author' (notice the ')", new [] {"A \"book\"", "Another 'book'","NoSpace(ButCharacterThatShouldBeEscaped)", "NoSpace'Quoted'", "NoSpace\"Quoted\""}}   // Test value I added
        };

        protected override void ProcessRecord()
        {
            WriteObject(string.Format("Author = {0}", _author));
            WriteObject(string.Format("Book = {0}", ((DynParamQuotedString) MyInvocation.BoundParameters["Book"]).OriginalString));
        }

        // Making this static means it should keep track of the last author used
        static string _author;
        public object GetDynamicParameters()
        {
            // Get 'Author' if found, otherwise get first unnamed value
            string author = GetUnboundValue("Author", 0) as string;
            if (!string.IsNullOrEmpty(author)) { 
                _author = author.Trim('\'').Replace(
                    string.Format("{0}{0}", DynParamQuotedString.DefaultQuoteCharacter), 
                    DynParamQuotedString.DefaultQuoteCharacter
                ); 
            }

            var parameters = new RuntimeDefinedParameterDictionary();

            bool isAuthorParamMandatory = true;
            if (!string.IsNullOrEmpty(_author) && m_dummyData.ContainsKey(_author)) {
                isAuthorParamMandatory = false;
                var m_bookParameter = new RuntimeDefinedParameter(
                    "Book",
                    typeof(DynParamQuotedString),
                    new Collection<Attribute>
                    {
                        new ParameterAttribute {
                            ParameterSetName = "BookStuff",
                            Position = 1,
                            Mandatory = true
                        },
                        new ValidateSetAttribute(DynParamQuotedString.GetQuotedStrings(m_dummyData[_author])),
                        new ValidateNotNullOrEmptyAttribute()
                    }
                );

                parameters.Add(m_bookParameter.Name, m_bookParameter);
            }

            // Create author parameter. Parameter isn't mandatory if _author
            // has a valid author in it
            var m_authorParameter = new RuntimeDefinedParameter(
                "Author",
                typeof(DynParamQuotedString),
                new Collection<Attribute>
                {
                    new ParameterAttribute {
                        ParameterSetName = "BookStuff",
                        Position = 0,
                        Mandatory = isAuthorParamMandatory
                    },
                    new ValidateSetAttribute(DynParamQuotedString.GetQuotedStrings(m_dummyData.Keys.ToArray())),
                    new ValidateNotNullOrEmptyAttribute()
                }
            );
            parameters.Add(m_authorParameter.Name, m_authorParameter);

            return parameters;
        }

        /*
            TryGetProperty() and GetUnboundValue() are from here: https://gist.github.com/fearthecowboy/1936f841d3a81710ae87
            Source created a dictionary for all unbound values; I had issues getting ValidateSet on Author parameter to work
            if I used that directly for some reason, but changing it into a function to get a specific parameter seems to work
        */

        object TryGetProperty(object instance, string fieldName) {
            var bindingFlags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public;

            // any access of a null object returns null. 
            if (instance == null || string.IsNullOrEmpty(fieldName)) {
                return null;
            }

            var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);

            if (propertyInfo != null) {
                try {
                    return propertyInfo.GetValue(instance, null);
                } 
                catch {
                }
            }

            // maybe it's a field
            var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);

            if (fieldInfo!= null) {
                try {
                    return fieldInfo.GetValue(instance);
                } 
                catch {
                }
            }

            // no match, return null.
            return null;
        }

        object GetUnboundValue(string paramName) {
            return GetUnboundValue(paramName, -1);
        }

        object GetUnboundValue(string paramName, int unnamedPosition) {

            // If paramName isn't found, value at unnamedPosition will be returned instead
            var context = TryGetProperty(this, "Context");
            var processor = TryGetProperty(context, "CurrentCommandProcessor");
            var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
            var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;

            if (args != null) {
                var currentParameterName = string.Empty;
                object unnamedValue = null;
                int i = 0;
                foreach (var arg in args) {
                    var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
                    if (isParameterName != null && true.Equals(isParameterName)) {
                        string parameterName = TryGetProperty(arg, "ParameterName") as string;
                        currentParameterName = parameterName;

                        continue;
                    }

                    // Treat as a value:
                    var parameterValue = TryGetProperty(arg, "ArgumentValue");

                    if (currentParameterName != string.Empty) {
                        // Found currentParameterName's value. If it matches paramName, return
                        // it
                        if (currentParameterName.Equals(paramName, StringComparison.OrdinalIgnoreCase)) {
                            return parameterValue;
                        }
                    }
                    else if (i++ == unnamedPosition) {
                        unnamedValue = parameterValue;  // Save this for later in case paramName isn't found
                    }

                    // Found a value, so currentParameterName needs to be cleared
                    currentParameterName = string.Empty;
                }

                if (unnamedValue != null) {
                    return unnamedValue;
                }
            }

            return null;
        }
    }
}

关于c# - Powershell 模块 : Dynamic mandatory hierarchical parameters,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29607287/

相关文章:

c# - linq select 和 group by

c# - 如何将 Mono Winforms 应用程序部署到 Suse Linux 11.0 Server Enterprise?

powershell - Powershell在函数调用后忽略表达式

powershell - 在Powershell中转义args [0]

azure - 是否可以使用 PowerShell 获取 Azure 订阅优惠或 OfferId?

c# - 这个。关闭();在窗口 wpf 中不起作用

c# - readData 用于 24 位 FLAC 和 WAV 文件

c# - DI 设计模式的值对象是否有效依赖?

powershell - 查找数组中最大的时间

mysql - 将AD用户的密码与mysql数据库同步