Using and abusing dynamic parameters.

Today I would like to share “creative” way of abusing dynamic parameters in PowerShell I came up with yesterday. All started with challenging question on PowerShell TechNet forum. But before I get there I would like to show also why and how you would use them normally, so that you know why I would call my trick an “abuse”. Smile All examples will be pretty naive and not production-oriented. For better examples – lookup poshcode, especially Joel’s and Oisin’s magical tools. Few to start with:

Why?

If you want your command to cover several similar yet slightly different scenarios, than most of time you could get away with ParameterSets. At times, however, you would have to come up with some bizarre parameters to cover everything. E.g. you want your command to give option to –Capitalize the name but only if the –Name contains lower chars at word boundary. Obviously, I can have –NameStartingWithoutUpperCase and –NameStartingWithUpperCase, one in parameter set with and other without –Capitalize switch, but it’s… ugly. Dynamic parameters give us option to have a “peek” at our $Name and decide – give user option to –Capitalize or not. So it gives us flexibility to add parameters when needed.

How?

That is actually pretty tough question. I could not get my head around this concept and even reading Joel’s and Oisin’s examples was not helping much until yesterday, when I decided I have to start using it. Winking smile Probably it was too develop-ish and .NET-ish for me. Winking smile

First you may want to read documentation. Enough for you to understand whole concept? Well, than I’m impressed. Smile For me it was not obvious enough so I mainly did a try and error thing to finally get my solution working. But first: our slightly more “expected” scenario. Here is how my function would look like with dynamic parameters used instead of parameter sets with some validation and crazy names:

function Get-Greeting {            
[CmdletBinding()]            
param (            
    $Name            
)            
            
DynamicParam {            
    if ($Name -cmatch '\b[a-z]') {            
                    
        # For blog width...            
        # As you can see - also abusing splatting.            
        # Here - to pass positional parameter TypeName            
            
        $Type = @(            
            'Management.Automation.ParameterAttribute'            
        )            
        $Attributes = New-Object @Type            
        $Attributes.ParameterSetName = "__AllParameterSets"            
        $Attributes.Mandatory = $false            
            
        $Type = @(            
            'Collections.ObjectModel.Collection[Attribute]'            
        )            
        $AttributeCollection = New-Object @Type            
        $AttributeCollection.Add($Attributes)            
                    
        $MyParameter = @{            
            TypeName = 'Management.Automation.RuntimeDefinedParameter'            
            ArgumentList = @(            
                'Capitalize' <# name #>            
                [switch] <# ParameterType #>            
                , $AttributeCollection <# Attributes #>            
            )            
        }            
        $Dynamic = New-Object @MyParameter            
                        
        $Type = @(            
            'Management.Automation.RuntimeDefinedParameterDictionary'            
        )            
        $ParamDictionary = New-Object @Type            
        $ParamDictionary.Add("Capitalize", $Dynamic)            
        $ParamDictionary            
    }            
}            
            
end {            
    if($PSBoundParameters.Capitalize) {            
        $Name = [regex]::Replace(            
            $Name,            
            '\b\w',            
            { $args[0].Value.ToUpper() }            
        )            
    }            
            
    "Welcome $Name!"            
}            
            
}

One thing to remember: if you add DynamicParam you have to have begin/process/end to wrap your code. Otherwise it won’t work. Either DynamicParam will be seen as command (and in effect – fail) or PowerShell will complain about curly braces. Here I’ve used ‘end’ block, because I do not plan to use pipeline at all. If you run this function and pass name that have some lower case letters where those probably should not be – you will get an option to –Capitalize them. So both will produce same results, with last one giving you error instead:

# Work as expected...            
Get-Greeting -Name Bartek            
Get-Greeting -Name bartek -Capitalize            
# Fails with "Parameter not found"            
Get-Greeting -Name Bartek -Capitalize            

Tab will work with it most of time, unless it won’t be able to tell quickly if parameter should be there or not. In such a case it will assume not.

Why abuse?

Question on the forum was simple: is it possible to run code before param block. Or rather: how to run code to avoid issues with fixing parameter type when this type may not be loaded before function is executed. OP was trying to use ValidateScript to do that (I would also probably start there) but it did not help. With dynamic parameters that is no-issue. You can run pretty long script before you actually start binding anything to the parameter of your choice. If you force it to be always available – tab will pick it up in any possible scenario. The sky is the limit. My original code was for creating Enum with Add-Type required Enum was not there yet. Having parameter that is an enum may be handy at times, but if you want to have custom one – you need to add it first. If you asking yourself – why do it inside function – well, to be honest I have no idea. But I’m not to judge, I’m to code. Winking smile My take would probably include module. I have tendency to make almost everything a module to simplify and isolate things. But OP wanted to have all in one, black box, so let’s do it. Smile

How to get there?

Well, all we need to do is modify our previous function a little bit. We have different parameter name, no condition to check (in forum post I’ve used if ($true) but it’s not necessary), and we want to run small try-catch to add-type if it’s not there yet:

DynamicParam {            
    try {             
        New-Object MyEnum | Out-Null            
    } catch {            
        Add-Type 'using System; public enum MyEnum { foo, bar }'            
    }            
                
    # ... same code... with some changes here:            
    $MyParameter = @{            
        TypeName = 'Management.Automation.RuntimeDefinedParameter'            
        ArgumentList = @(            
            'Enum' <# name #>            
            [MyEnum] <# ParameterType #>            
            , $AttributeCollection <# Attributes #>            
        )            
    }            
            
    # ... and here:            
            
    $ParamDictionary.Add("Enum", $Dynamic)            
    $ParamDictionary            
}            
            
end {            
                
    $PSBoundParameters.Enum            
    $PSBoundParameters.Enum.GetType().FullName            
            
}

The result is exactly what OP wanted (I hope Winking smile ):

DynamicParamAbuse

It’s not something you could not live without. But for me it was good reason to finally try to use DynamicParam(s). And I can see few situations when it would actually make sense in production-oriented scripts. The only danger now is that I will use them far too often. Winking smile

About these ads

3 thoughts on “Using and abusing dynamic parameters.

    • If your question is: how to create more than one dynamic parameter than answer would be: just create more parameters (using class RuntimeDefinedParameter) and add all of them to RuntimeDefinedParameterDictionary, that you will return as the last step in DynamicParam scriptblock. This is a collection, so you should be able to add as many parameters as you need, obviously – each can be defined/ added based on different condition.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s