Dynamic Parameter question

This topic contains 9 replies, has 4 voices, and was last updated by Profile photo of Micah Battin Micah Battin 1 year, 10 months ago.

  • Author
    Posts
  • #22372
    Profile photo of Micah Battin
    Micah Battin
    Participant

    Hi everyone,

    I have a quick question here. Attached is the beginnings of a custom set of WSUS related functions to help us patch.

    To use this function, you will need a WSUS Server with at least 1 computer checking into it as well as a WSUS group that has a space in the name. You will also need WMI and ICMP access to all computers you are targeting with this function.

    A simple example of the function is:

     Update-WSAPatchStatus -Server "MyWSUSServer.Domain.Com" -WsusGroup "My Group" | Select-Object * 

    The intent of -WSUSGroup being a dynamic parameter is that it automatically pulls the group names from wsus as a ValidateSet and thus, you can tab through them or if you are in ISE, you can select what you are looking for from a drop menu. The problem I have is when you tab or select from a drop menu, Powershell isnt wrapping names with spaces in them in quotes. So my team has to go back after they selected a group, and quote it.

    Does anyone have any idea how I can fix this?

  • #22374
    Profile photo of K. Chris Nakagaki
    K. Chris Nakagaki
    Participant

    I'm thinking it might make most sense to make another function that 'Get-WSUSGroups' to return objects that your WSUSGroups parameter. That way you can pipe or use where clauses.

  • #22375
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    That's a limitation of PowerShell's current tab expansion functionality, unfortunately. I don't think there's anything you can do about it.

    There is a bug filed on the Connect site, but it's been there for over a year now. Not sure if / when this will become a priority: https://connect.microsoft.com/PowerShell/feedback/details/812233/auto-completed-values-with-spaces-do-not-have-quotes-around-them

  • #22376
    Profile photo of Micah Battin
    Micah Battin
    Participant

    I had considered that, but I don't know that it would change the ability for me to import a list dynamically as a parameter set for a parameter. I would still think I would have the same problem with powershell not parsing my dynamic parameter's data set correctly.

  • #22377
    Profile photo of Micah Battin
    Micah Battin
    Participant

    Darn, thanks Dave.

  • #22378
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Actually, I just fiddled around with this a bit, and I was able to get it working by putting a modified TabExpansion2 function into my profile. However, this is something that would be tricky to deploy with your cmdlet without potentially stomping on any other custom modifications (such as someone using the TabExpansion++ module, etc.)

    Here's the code:

    function TabExpansion2
    {
        
    
        [CmdletBinding(DefaultParameterSetName = 'ScriptInputSet')]
        Param(
            [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)]
            [string] $inputScript,
        
            [Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 1)]
            [int] $cursorColumn,
    
            [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 0)]
            [System.Management.Automation.Language.Ast] $ast,
    
            [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 1)]
            [System.Management.Automation.Language.Token[]] $tokens,
    
            [Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 2)]
            [System.Management.Automation.Language.IScriptPosition] $positionOfCursor,
        
            [Parameter(ParameterSetName = 'ScriptInputSet', Position = 2)]
            [Parameter(ParameterSetName = 'AstInputSet', Position = 3)]
            [Hashtable] $options = $null
        )
    
        End
        {
            if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet')
            {
                $completion = [System.Management.Automation.CommandCompletion]::CompleteInput(
                      $inputScript,
                     $cursorColumn,
                          $options)
            }
            else
            {
                $completion = [System.Management.Automation.CommandCompletion]::CompleteInput(
                                  $ast,
                               $tokens,
                     $positionOfCursor,
                              $options)
            }
    
            $count = $completion.CompletionMatches.Count
            for ($i = 0; $i -lt $count; $i++)
            {
                $result = $completion.CompletionMatches[$i]
    
                if ($result.CompletionText -match '\s')
                {
                    $completion.CompletionMatches[$i] = New-Object System.Management.Automation.CompletionResult(
                        "'$($result.CompletionText)'",
                        $result.ListItemText,
                        $result.ResultType,
                        $result.ToolTip
                    )
                }
            }
    
            return $completion
        }
    }
    
  • #22379
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    That code is a very simple implementation that just puts single quotes around anything containing whitespace. There are other things it should check for, technically (such as commas, etc), but it at least proves that this can be done.

    Edit: And of course, our forum is stripping out anything in angle brackets because it thinks they should be HTML, not a PowerShell comment. 😛 Oh well, at least all the executable code is there, even if it lines up weirdly.

  • #22381
    Profile photo of Rohn Edwards
    Rohn Edwards
    Participant

    This is similar to the problem from this post: https://powershell.org/forums/topic/cant-get-dynamicparam-to-work-when-set-values-include-commas/

    Like Dave said, you can modify TabExpansion2 (which is a cool solution, but, like Dave says, can mess up other custom modifications), or you can try to use a C# helper class to put the quotes in for you. Here's an example that creates a function named TestFunction with a dynamic parameter named -DynamicParam. Try it out and see if it works and makes sense:

    Add-Type @"
        public class DynParamQuotedString {
     
            public DynParamQuotedString(string quotedString) : this(quotedString, "'") {}
            public DynParamQuotedString(string quotedString, string quoteCharacter) {
                OriginalString = quotedString;
                _quoteCharacter = quoteCharacter;
            }
    
            public string OriginalString { get; set; }
            string _quoteCharacter;
    
            public override string ToString() {
                if (OriginalString.Contains(" ")) {
                    return string.Format("{1}{0}{1}", OriginalString, _quoteCharacter);
                }
                else {
                    return OriginalString;
                }
            }
        }
    "@
    
    $__DynamicParamName = "DynamicParam"
    $__DynamicParamValidateSet = @(
        "A string with spaces"
        "Another string with spaces"
        "StringWithoutSpaces"
    )
     
    function TestFunction {
        [CmdletBinding()]
        param(
        )
    
        dynamicparam {
            $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
     
            $Attributes = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $Attributes.Add( (New-Object System.Management.Automation.ParameterAttribute) )
    
            # Convert each object into a DynParamQuotedString:
            $Attributes.Add( (New-Object System.Management.Automation.ValidateSetAttribute($__DynamicParamValidateSet | % { [DynParamQuotedString] $_.ToString() })) )
    
            $ParamDictionary.$__DynamicParamName = New-Object System.Management.Automation.RuntimeDefinedParameter (
                $__DynamicParamName,
                [DynParamQuotedString],  # Notice the type here
                $Attributes
            )
    
            return $ParamDictionary
        } 
    
        process {
            
            $ParamValue = $null
            if ($PSBoundParameters.ContainsKey($__DynamicParamName)) {
                # Get the original string back:
                $ParamValue = $PSBoundParameters.$__DynamicParamName.OriginalString
            }
    
            "$__DynamicParamName = $ParamValue"
        }
    }
    
  • #22382
    Profile photo of Micah Battin
    Micah Battin
    Participant

    You guys are awesome. I will test the C# class implementation in a few, and if I cant get that working, I will override the tabexpansion with Dave's solution (this function runs from a box that doesn't have any other scripting/development going on, so it will be okay, although I will likely try just putting it into the module).

  • #22389
    Profile photo of Micah Battin
    Micah Battin
    Participant

    So the [DynParamQuotedString] class worked awesomely, even when I declared it inside of my DynamicParam scriptblock. It even plays nice when I declare it as an array.

    Thanks, you guys are a life saver.

You must be logged in to reply to this topic.