powershellparameterspowershell-corepowershell-7.3

How to set mandatory attribute of a parameter to $true conditionally in PowerShell?


The mandatory attribute accepts ScriptBlocks but as I've tested it and seen in other answers, it's always returning $true

function Test-Function {
    param (
        [Parameter (Position = 0)]
        [string]$First,

        [Parameter (Position = 1, Mandatory = ({
            return $false
        }))]
        [string]$Second
    )
    Write-Host "First: $First"
    Write-Host "Second: $Second"
}
Test-Function -First "boo"

I don't want to use Dynamic parameters because Get-Help doesn't show them (unless I'm missing something?), and I'm already using ParameterSets a lot, so can't break their logic.

What are some other ways I can achieve what I want?

my goal is to first read from a user configuration file and if a value for a parameter is already available in that file then the parameter should turn from mandatory to optional.


Solution

  • Note:


    The Mandatory attribute accepts ScriptBlocks

    It technically accepts a script block, but passing one is pointless, given that the property's type is [bool]: [bool] { <# whatever #> } is always $true, as with any non-primitive object cast to [bool].[1]

    Only attributes explicitly designed to accept script blocks support them meaningfully, typically via the attribute constructor rather than (only) via one of its properties (e.g. [ValidateScript({ <# whatever #> })])


    I don't want to use dynamic parameters because Get-Help doesn't show them

    Get-Help (and the -? parameter) do show them, but only if the runtime conditions for their addition are met.

    Therefore, the only way to always make a dynamic implementation of your -Second parameter show would be to add it unconditionally inside the DynamicParam block used to implement dynamic parameters, and to only make its Mandatory property dynamic, depending on runtime conditions.

    However:


    The following alternative to your solution moves all processing into the $(...) subexpression:

    function Test-Function {
      param (
          [Parameter(Position = 0)]
          [string] $First
          ,
          [Parameter(Position = 1)]
          [ValidatePattern('^[a-zA-Z0-9 ]+$')]
          [string] $Second = $(            
              # Try to obtain a default value and make sure one is available.
              $val = (Get-Content -Path "$env:USERPROFILE\.WDACConfig\UserConfigurations.json" | ConvertFrom-Json).policyname
              if ($null -eq $val) { 
                throw "A -Second argument is required or must be preconfigured."
              }
              # A default value is available:
              # Validate it based on the [ValidatePattern()] attribute.
              $validatePatternAttrib = $MyInvocation.MyCommand.Parameters['Second'].Attributes.Where({ $_ -is [System.Management.Automation.ValidatePatternAttribute]})
              if (-not [regex]::Match($val, $validatePatternAttrib.RegexPattern, $validatePatternAttrib.Options).Success) {
                throw ("The argument `"$val`" does not match the `"$($validatePatternAttrib.RegexPattern)`" pattern.", ($validatePatternAttrib.ErrorMessage -f $val))[[bool] $validatePatternAttrib.ErrorMessage]
              }
              $val # Output the default value to assign it to the parameter var.
          )
      )
      process {
          Write-Host "First: $First"
          Write-Host "Second: $Second"
      }
    }
    Test-Function -First "boo"
    

    Note:


    [1] PowerShell allows implicit conversion to [bool] from any type. See the bottom section of this answer for a summary of the rules.