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.
Note:
The next two sections make general observations.
The bottom section shows an alternative solution to your own.
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:
Dynamic parameters are generally nontrivial to implement.
Assigning a default value to a dynamic parameter - which is what you want - appears to be unsupported (assigning to the .Value
property of a dynamic parameter is in effect quietly ignored).
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:
While scoping the solution to the default-value code inside $(...)
is desirable, there is added complexity due to having to dynamically apply the [ValidatePattern()]
attribute to the default value (the code could be simplified; as shown it fully emulates what the [ValidatePattern()]
does).
Arguably, this added complexity shouldn't be necessary, as even a default value should automatically be subject to the same validation as an explicitly passed one. GitHub issue #8795 proposes just that.
[1] PowerShell allows implicit conversion to [bool]
from any type. See the bottom section of this answer for a summary of the rules.