red888
red888

Reputation: 31672

Can I make one parameter mandatory based on the _value_ of a different parameter?

If param One is set to bbb then I want to make param Two mandatory. Is it possible to do this with parameter sets somehow or will I just have to add this logic to the script itself?

Example idea of what I need:

 param (
        [Parameter(Mandatory, ParameterSetName = 'default')]
        [ValidateSet(“aaa”, ”bbb”, ”ccc”, "ddd")]
        [String]
        $One,

        [Parameter(ParameterSetName = 'default')]
        [ValidateScript( { $One -eq 'bbb'; THEN MAKE THIS PARAM MANDATORY! })]
        [String]
        $Two
    )

The value of $One seems to not be set yet as I tried doing this and one.txt is empty

[ValidateScript( { $One > One.txt; $true })]

Edit

While there is DynamicParam{} that looks like its only if you have begin,process,end, etc setup. This is a simple function that I don't want to add that to it. Also DynamicParam seems to require a bizarre amount of boilerplate code to work

Edit

It looks like DynamicParam is the only way, but I think it crazy. It's weird and not readable, but I'd still prefer Powershell to handle validation for me.

Doing it myself though is still pretty simple:

if ($One -eq 'bbb' -and -not $Two) {
    ThrowError Param Two required when One set to $One
}

Upvotes: 4

Views: 1293

Answers (2)

mklement0
mklement0

Reputation: 440337

Use the default value for parameter -Two to enforce the desired logic, via an if statement that uses Throw:

param (
  [Parameter(Mandatory, ParameterSetName = 'default')]
  [ValidateSet('aaa', 'bbb', 'ccc', 'ddd')]
  [String]
  $One,

  [Parameter(ParameterSetName = 'default')]
  [String]
  $Two = $(if ($One -eq 'bbb') { Throw "-Two must be passed if -One equals 'bbb'." })
)

"-One: $One; -Two: $Two"

Note: Emulating a (conditionally) mandatory parameter with Throw means that the behavior differs from a regular Mandatory parameter in that the latter prompts when a value isn't given.

A solution based on validation attributes would be preferable, but validation attributes aren't designed for cross-parameter validation, and no particular order of their evaluation is guaranteed.

The above solution relies on the fact that default values are evaluated after explicitly passed arguments have been bound.

The much more verbose alternative is to use a dynamic parameter, as shown in Wasif Hasan's answer, relies on the same timing, though it does have the advantage of exhibiting normal prompt-for-a-missing-mandatory-value behavior.

As of this writing, Wasif's answer doesn't work as posted, so here's a working solution; note how the use of DynamicParam syntactically requires (at least one of) the begin, process and end blocks.

# Syntax requires PSv5+:
using namespace System.Management.Automation

param( 
  [Parameter(Mandatory, ParameterSetName='default')]
  [ValidateSet('aaa', 'bbb', 'ccc', 'ddd')]
  [String]$One
)

# Define parameter -Two *dynamically*, so that its Mandatory property
# can be based on the specific value of the already-bound static -One parameter.
DynamicParam {

  # Create a the dictionary of dynamic parameters.
  $dict = [RuntimeDefinedParameterDictionary]::new()

  # Define and add the -Two parameter 
  $paramName = 'Two'
  $dict.Add(
    $paramName,
    [RuntimeDefinedParameter]::new(
      $paramName,
      [string],
      [ParameterAttribute] @{
        ParameterSetName = 'default'
        # *Conditionally* make the parameter mandatory, depending on the value
        # of the already-bound static -One parameter.
        Mandatory = $One -eq 'bbb'
      }
    )
  )

  # Return the dictionary
  return $dict
}

begin {

  # NOTE: Dynamic parameter values do not become local variables the way
  #       they do for static parameters; the value must be accessed via the
  #       automatic $PSBoundParameters dictionary.
  $Two = $PSBoundParameters['Two']

  "-One: $One; -Two: $Two"

}

Upvotes: 5

wasif
wasif

Reputation: 15518

You are looking for dynamic parameters: Conditional PowerShell parameters

param( 
  [String]$One
)

DynamicParam {
  # Create a parameter dictionary
  $runtimeParams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

  # Populate it with parameters, with optional attributes

  $attribs = New-Object  System.Collections.ObjectModel.Collection[System.Attribute]

  $mandatoryAttrib = New-Object System.Management.Automation.ParameterAttribute
  $mandatoryAttrib.Mandatory = ($one -eq "bbb")
  $attribs.Add($mandatoryAttrib)

  $param = New-Object System.Management.Automation.RuntimeDefinedParameter('Two', String, $attribs)
}
Begin {
  # If desired, move dynamic parameter values to variables
  $ParameterName = $PSBoundParameters['Two']
}

  # Do like regular

Without process block it will work.

Upvotes: 0

Related Questions