Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to reuse a 'param' block across multiple functions?

Suppose I have a script with multiple functions taking the exact same parameters, in the same positions and with the same types and constraints, like this:

function Verb1-MyValue {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)][String]$Param1,
        [parameter(Mandatory = $true)][String]$Param2,
        [ValidateSet("Value1","Value2")][String]$Param3 = "Value1"
    )
    # code ...
}

function Verb2-MyValue {
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)][String]$Param1,
        [parameter(Mandatory = $true)][String]$Param2,
        [ValidateSet("Value1","Value2")][String]$Param3 = "Value1"
    )
    # code ...
}

# and so on ...

I wanted to share the param block with all functions to avoid potential problems (they need to be the same for all of them) and to avoid the redundancy.

Is there a way in PowerShell to share the param block across multiple functions in the same script? If not, are there any alternatives to it?

like image 344
julealgon Avatar asked Nov 13 '14 14:11

julealgon


2 Answers

If you are able to, I would recommend going with the "C# project which builds a powershell module" approach. Depending on your situation, there are a variety of benefits some of which include:

  1. Compile-time safety. I know a few developers would prefer compiled /strongly typed languages because of the added safety
  2. Easier to write automated tests. This may be debatable, but in my opinion, having libraries like nUnit and other testing frameworks are a huge plus
  3. Language familiarity. I have encountered many devs that are familiar with c#, but not powershell, and so they struggle.

To get started, I found this article. Basically, it says to add a reference to System.Management.Automation.dll to your project, and then a very basic cmdlet would look as follows:

using System;
using System.Collection.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;

namespace MyModule
{
    [Cmdlet(VerbsCommon.Get, "Saluation")]
    public class GetSaluation : PSCmdlet
    {
        private string[] nameCollection;

        [Parameter(
            Mandatory = true,
            ValueFromPipelineByPropertyName = true,
            ValueFromPipelin = true,
            Position = 0,
            HelpMessage = "Name to get salutation for."
        )]
        [Alias("Person", "FirstName")]
        public string[] Name
        {
            get { return nameCollection;}
            set { nameCollection = value;}
        }

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }

        protected override void ProcessRecord()
        {
            foreach (string name in nameCollection)
            {
                WriteVerbose("Creating salutation for " + name);
                string salutation = "Hello, " + name;
                WriteObject(salutation);
            }
        }

        protected override void EndProcessing()
        {
            base.EndProcessing();
        }

    }

Then, to use this module, open a powershell prompt, navigate to where your dll is built and use the Import-Module cmdlet.

And then for your specific question (How do I reuse param blocks with different cmdlets?) you can have a base cmdlet which defines the parameters, and all of the cmdlets you wish to write can inherit that from the base class.

like image 195
Travis Avatar answered Sep 29 '22 12:09

Travis


I'd recommend using Travis' suggestion and go the compiled cmdlet route. You asked in one of your comments if this was possible with just script, though, so I'm going to try to provide an example of doing that. Advanced functions support creating dynamic parameters, and you can combine that capability with the Get-Command cmdlet to create dynamic versions of a specified command's parameters:

function GetDynamicParamDictionary {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [string] $CommandName
    )

    begin {
        # Get a list of params that should be ignored (they're common to all advanced functions)
        $CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | 
            Get-Member -MemberType Properties | 
            Select-Object -ExpandProperty Name
    }

    process {

        # Create the dictionary that this scriptblock will return:
        $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

        # Convert to object array and get rid of Common params:
        (Get-Command $CommandName | select -exp Parameters).GetEnumerator() | 
            Where-Object { $CommonParameterNames -notcontains $_.Key } |
            ForEach-Object {
                $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter (
                    $_.Key,
                    $_.Value.ParameterType,
                    $_.Value.Attributes
                )
                $DynParamDictionary.Add($_.Key, $DynamicParameter)
            }

        # Return the dynamic parameters
        $DynParamDictionary
    }
}

function TestFunction {
    # Create some dummy params
    param(
        [string] $StringParam,
        [switch] $Switch1,
        [switch] $Switch2,
        [int] $IntParam
    )
}

function MimicTestFunction {
    [CmdletBinding()]
    # Empty param block (you could add extra params here)
    param()

    # Mimic TestFunction's parameters
    dynamicparam { GetDynamicParamDictionary TestFunction }
    process { $PSBoundParameters }
}

function MimicGetChildItem {
    [CmdletBinding()]
    param()

    dynamicparam { GetDynamicParamDictionary Get-ChildItem }
    process { $PSBoundParameters }
}

This should work except when a the reference command also has dynamic parameters.

like image 31
Rohn Edwards Avatar answered Sep 29 '22 11:09

Rohn Edwards