Search code examples
c#powershellpowershell-cmdlet

How can I provide a tab completion list, with the values generated on powershell startup?


So I have a cmdlet written in c#: Get-LivingCharacter. I want users to use this like Get-LivingCharacter -Name "Bran", but I would like to allow for the list of available characters to change. Maybe today, "Bran" is a valid name to pass in for Get-LivingCharacter, but maybe in the future it will not be. Things happen.

For convenience I want to allow tab-completion of this field. However, I can't seem to get that to work for non-const data sets. Dynamic fields don't even auto-complete the field name, nevermind the value, and I don't know a way to implement this for a non-dynamic field. Conceptually, I could generate a .ps1 file on startup given the current data set, and then load that ps1 as the module, but this feels a bit like killing a pup with a greatsword - lots of overkill. Is there a better option?


Solution

  • I had already implemented a similar function to the DynamicParam helper function, as reference in the comments. However, tab completion wasn't working. I was writing a minimal reproduction example, when...my tab completion worked.

    It turns out, it reproducibly works/breaks based on the inclusion of a WriteDebug statement:

    [Cmdlet("Get", "LivingCharacter")]
    public class GetLivingCharacter : Cmdlet, IDynamicParameters
    {
        protected override void ProcessRecord()
        {
        }
    
        public object GetDynamicParameters()
        {
            WriteDebug("Getting names"); // Tab completion won't work with this here - comment it out and it works.
            ^^^^^^^^^^
    
            var chars = new List<String>() { "Bran", "Arya" };            
            var dict = new RuntimeDefinedParameterDictionary();
    
            var attributes = new Collection<Attribute>
            {
                new ParameterAttribute
                {
                    HelpMessage = "Enter a valid open name",
                    Mandatory = true
                },
                new ValidateSetAttribute(chars.ToArray()),
            };
    
            dict.Add("Name", new RuntimeDefinedParameter("Name", typeof(string), attributes));
    
            return dict;
        }
    }
    

    After some digging, the WriteDebug statement is throwing (which I assume is because it can't output while I'm typing). It then recreates the GetLivingCharacter class after I've finished the command to validate. It took a while to find since, because of the issue, I can't write the error to the console, so I had to append to a temp file instead.