Conditional Mandatory in PowerShell - powershell

I'm trying to make a parameter mandatory, but only if another parameter uses certain ValidateSet values. It seems that using a code block on Mandatory doesn't work as expected.
function Test-Me {
[CmdletBinding()]
Param (
[Parameter()]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory = {-not ($Type -eq "NoNameRequired")})]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory? = '$(-not ($Type -eq "NoNameRequired"))'"
}
}

Set-StrictMode -Version Latest
function Test-Me {
[CmdletBinding(DefaultParameterSetName = "Gorgonzola")]
Param (
[Parameter(Mandatory)]
[int]
$Number,
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Number = '$Number'"
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Parameter sets seem to help simulate conditional mandatory parameters.
I can make it to where if either the Type or Name parameter is given, then they are both required. This can happen regardless of other parameters in the function, such as the sibling Number parameter above.
I set the default parameter set name to something random; I usually specify "None". That parameter set name doesn't need to actually exist, again indicated by the Number parameter.
All of this works regardless of your strict mode setting.

Related

Missing an argument for parameter on PS1

I was trying to figured out how can I throw exception or set it to default value, once I didn't put any value on the parameter?
Function ConnectionStrings {
param(
[Parameter(
Mandatory = $false)]
[AllowNull()]
[AllowEmptyString()]
[string] $region
)
try{
if (!$regionHash.ContainsKey($region)){
$regionHash["US"]
} elseif (!$region) {
$regionHash["US"]
#$slave = $regionHash["US"].slave
#$master = $regionHash["US"].master
#$track = $regionHash["US"].tracking
} else {
$regionHash[$region]
#$slave = $regionHash[$region].slave
#$master = $regionHash[$region].master
#$track = $regionHash[$region].tracking
}
} catch {
Write-Warning -Message "OOPS!"
}
}
Once I run the command: ConnectionStrings -region It should throw an exception or set to a default value instead.
Need your guidance, I'm just new on powershell.
Thanks.
edit: Without setting the value of the parameters ConnectionStrings -region
You cannot use both Mandatory and a default value for the same parameter. Why not? If a Param has the Mandatory` attribute, the cmdlet will prompt the user for a value, if no value is provided.
If you want to use a default value, provide one like this:
function Verb-Noun
{
Param
(
# Param1 help description
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
$Param1 = 'default'
)
"value of `$param1` is $Param1"
}
If executed like so
>Verb-Noun -Param1 a
value of $param1 is a
But if the user does not provide a value for the parameter, the default value will be used.
Verb-Noun
value of $param1 is default
Now, if we add the Mandatory attribute like so...
Param
(
# Param1 help description
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
$Param1 = 'default'
)
#rest of code...
When the user fails to provide a value for Param1, the user is prompted to provide a value. There is no opportunity for the default to be used.
Verb-Noun
cmdlet Verb-Noun at command pipeline position 1
Supply values for the following parameters:
Param1:
#function pauses and won't run until a value is provided

Powershell function parameters proper use

PARAM (
[parameter(Mandatory=$true)]
[string]$Poolname,
[array]$Ports = 443,
[parameter(Mandatory=$true)]
[ValidateSet("ELB","ALB")]
$Loadbalncertype,
[parameter(Mandatory=$true)]
[ValidateSet("Ping","HTTPGet")]
$HealthCheckConfigType,
[parameter(Mandatory=$true)]
[array]$LBSubnets,
[parameter(Mandatory=$true)]
[string]$SecGroupID,
[int]$IdleTimeoutsec = 60,
[bool]$SSLPassthrough = $false,
string]$SSLCertificateName,
[string]$HealthCheckPath,
[string]$SSLPolicyName,
[bool]$ConfigureProxyProtocol = $true
)
In the above I would like to use Parameter $HealthCheckConfigType only if the $Loadbalncertype = ELB. I am not sure how to create this logic in Powershell function parameter section.
To do that in the param definition specifically, you could use DynamicParam to create a dynamic parameter, but that's a lot of work and probably overkill.
The most straightforward method I can think of if you must leave $LoadBalancerType as a [string] is to use [ValidateScript()] like so:
param(
[ValidateSet("ELB","ALB")]
$LoadBalancerType ,
[ValidateScript( { $LoadBalancerType -eq 'ELB' } )]
[ValidateSet("Ping","HTTPGet")]
$HealthCheckConfigType
)
This will give a crappy error message, which you could override with a well-placed throw:
[ValidateScript( { $LoadBalancerType -eq 'ELB' -or $(throw 'A better error message') } )]
Another option is to change your $LoadBalancerType parameter into separate switch parameters, and use them to define parameter sets:
[CmdletBinding(DefaultParameterSet='ALB')]
param(
[parameter(Mandatory=$true)]
[string]$Poolname,
[Parameter(Mandatory, ParameterSetName = 'ALB')]
[Switch]$ALB ,
[Parameter(Mandatory, ParameterSetName = 'ELB')]
[Switch]$ELB ,
[Parameter(Mandatory, ParameterSetName = 'ELB')
[ValidateSet("Ping","HTTPGet")]
$HealthCheckConfigType
)
This lets the parameter parser enforce this restriction, and you can see it in the automatically generated parameter sets by calling Get-Help on your function.
And, even though this isn't the usual way, in your case if you name the parameter sets with the names of the values you wanted, you could recreate $LoadBalancerType without conditionals:
$LoadBalancerType = $PSCmdlet.ParameterSetName
(assuming of course, that the only possible parameter sets are load balancer names directly; be careful with this)
But if you never really needed that string value; i.e. if you were only ever going to do:
if ($LoadBalancerType -eq 'ALB') {
} elseif ($LoadBalancerType -eq 'ELB') {
}
or something like that, then you don't need to recreate it, just do:
if ($ALB) {
} elseif ($ELB) {
}
Alternatively, you don't have to do this check in the param block at all; you can do it in your function body, or begin/process blocks where appropriate.
Use a DynamicParam block: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-6#dynamic-parameters
I don't have a ton of experience with them myself, but here is the code sample from that MS doc. You can process and add any parameters you need:
function Get-Sample {
[CmdletBinding()]
Param ([String]$Name, [String]$Path)
DynamicParam
{
if ($path -match ".HKLM.:")
{
$attributes = New-Object -Type `
System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $false
$attributeCollection = New-Object `
-Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$dynParam1 = New-Object -Type `
System.Management.Automation.RuntimeDefinedParameter("dp1", [Int32],
$attributeCollection)
$paramDictionary = New-Object `
-Type System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("dp1", $dynParam1)
return $paramDictionary
}
}
}
Another option would be to not muck with DynamicParam and in your code body, simply ignore $HealthCheckConfigType if $LoadBalancerType does not equal ELB, but if you want to use parameter validators to check this, DynamicParam is the answer.

PowerShell ValidateSet on Boolean Parameter

I am attempting to use ValidateSet with a boolean parameter however I cannot get it to function as expect.
An example to replicate the issue:
function Set-Boolean
{
[CmdletBinding()]
[OutputType([Bool])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[ValidateSet($false,$true)]
[Bool] $Bool
)
Process
{
$Bool
}
}
At run time PowerShell expands the $true and $false variables upon tab completion to True and False respectively however this then cause the parameter validation to fail as only $true, $false, 1 and 0 are valid boolean types.
I tried changing ValidateSet to [ValidateSet('$false','$true')] and the tab completion works as expected however the parameter validation still fails as the parameter is expecting the strings '$false' and '$true'.
I could change ValidateSet to [ValidateSet([bool]0,[bool]1)] and have it function as I expect but I find 0 and 1 a poorer user experience over $true and $false for completion of a boolean parameter.
The expected output of the function should be True when $true is chosen and False when $false is chosen.
I have implemented a workaround in my code but I want to know how I can use ValidateSet with a boolean parameter, if it is possible, to enable the use of tab completion for the user.
Joining some answers, the best option will depend on the needs, if the parameter is type boolean it can be replaced by one of type switch, but if you want to specify the value to see in the code or by style; It can be done as follows.
function Write-SwitchParam {
[CmdletBinding()]
param (
[Switch] #[Switch] or [System.Management.Automation.SwitchParameter]
$SwitchParam
)
if($SwitchParam)
{
Write-Host "Switch is present. Do Positive Action."
}
else {
Write-Host "Switch isn't present. Do Negative Action."
}
}
function Write-BooleanParam {
[CmdletBinding()]
Param(
[parameter(Position=0, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
[ValidateSet($true, $false, 0, 1)]
$BoolParam = $true
)
Begin{
}
Process{
$value = [System.Convert]::ToBoolean($BoolParam)
Write-Host $value
}
End{
}
}
Write-Host "TESTS BOOLEAN PARAM" -ForegroundColor Magenta
Write-BooleanParam -BoolParam $true
Write-BooleanParam -BoolParam $false
Write-Host
Write-BooleanParam $false
Write-BooleanParam $true
Write-Host
Write-BooleanParam True
Write-BooleanParam False
Write-Host
Write-BooleanParam 0
Write-BooleanParam 1
Write-Host
Write-BooleanParam
Write-Host
$true, $false, "True", "False", 1, 0 | Write-BooleanParam
Write-Host
Write-Host "TESTS SWITCH PARAM" -ForegroundColor Magenta
Write-SwitchParam
Write-SwitchParam -SwitchParam
You can use ValidateSet for autocompletion without specifying the input variable type. Though, you cannot use 0 and 1 as input in this case:
function Set-Boolean
{
[CmdletBinding()]
[OutputType([Bool])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[ValidateSet($false,$true)]
$Bool
)
Process
{
[System.Convert]::ToBoolean($Bool)
}
}
This gives the tab completion for the user, as desired using ValidateSet, but is achieved by registering an argument completer instead (especially in the ISE);
function Set-Boolean
{
[CmdletBinding()]
[OutputType([Bool])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[Bool] $Bool
)
Process
{
$Bool
}
}
Register-ArgumentCompleter -CommandName 'Set-Boolean' -ParameterName 'Bool' -ScriptBlock {
[System.Management.Automation.CompletionResult]::new(
'$False',
'$False',
'ParameterValue',
'[bool] False'
)
[System.Management.Automation.CompletionResult]::new(
'$True',
'$True',
'ParameterValue',
'[bool] True'
)
}

DynamicParam not working when prompt for input

I'm trying to get my powershell script to prompt for input, but still use a dynamicparam. If I pass the parameters through the command line (Example: DeployBuild "DEV02" "ClientPortal" "a" "b") , then the below code works fine, and I get prompted for the dynamic param. If I chose not to pass the parameters via command line (Example: DeployBuild), and instead let the script prompt for input, the dynamic parameter quits working. Does anyone have an idea why this is not working?
Function DeployBuild {
[CmdletBinding()]
Param
(
[ValidateSet("DEV01","DEV02","PEDEV01","QA01","QA02","UAT","PERF01","PROD")]
[Parameter(Mandatory=$true, Position=1)]
[String]$environment,
[Parameter(Mandatory=$true, Position=2)]
[ValidateSet("AuthenticationService","ClientPortal")]
[String]$application,
[Parameter(Mandatory=$true, Position=3)]
[String]$buildName,
[Parameter(Mandatory=$true, Position=4)]
[String]$buildNumber
)
DynamicParam{
if ($environment -eq "DEV02"){
#create a new ParameterAttribute Object
$buildVersionAttribute = New-Object System.Management.Automation.ParameterAttribute
$buildVersionAttribute.Position = 5
$buildVersionAttribute.Mandatory = $true
#create an attributecollection object for the attribute just created.
$attributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
#add our custom attribute
$attributeCollection.Add($buildVersionAttribute)
#add our paramater specifying the attribute collection
$buildVersionParam = New-Object System.Management.Automation.RuntimeDefinedParameter('buildVersion', [double], $attributeCollection)
#expose the name of our parameter
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add('buildVersion', $buildVersionParam)
return $paramDictionary
}
}
End{
Write-Host $environment
Write-Host $application
Write-Host $buildName
Write-Host $buildNumber
Write-Host $PSBoundParameters.buildVersion
}
}
Well this is really old so you probably don't need it anymore, but I'm pretty sure the reason this is happening is because of the conditional in the DynamicParam block that looks to see what the value of $environment is.
When the DynamicParam block is evaluated, $environment is blank, so the parameter is never added.
You can demonstrate this for yourself like this:
DynamicParam {
Write-Verbose "DynamicParam here, environment is: '$environment'" -Verbose
# ... rest of code here
}
Now run DeployBuild with no parameters and you'll see exactly when the block is being evaluated.

Can a Powershell v2.0 dynamic parameter be at position 0?

I cannot find this specific question being addressed, so I'll ask it here:
I cannot seem to get a dynamc parameter to be the position 0 parameter. When I try, it seems as though the first static parameter defined at position 1 will promote or inherit position 0 and the dynamic parameter defined for position 0 is then added afterward at the next available position (position 1):
$x=[string]::Empty;
Function foo {
[cmdletbinding()]
Param (
[Parameter(ParameterSetName="set1",
Position=1,
ValueFromPipeline=$true)]
$InputObject,
[Parameter()]
[switch]
$RequireFilePath
)
DynamicParam {
$mand = $script:x -eq $null -or `
$script:x -eq [string]::Empty -or `
$RequireFilePath.IsPresent;
$attrs = New-Object System.Management.Automation.ParameterAttribute;
$attrs.ParameterSetName = "set1";
$attrs.Mandatory = $mand;
$attrs.Position = 0;
$attrCollection = New-Object `
System.Collections.ObjectModel.Collection[System.Attribute];
$attrCollection.Add($attrs);
$FilePath = New-Object System.Management.Automation.RuntimeDefinedParameter `
"FilePath", string, $attrCollection;
$paramDictionary = New-Object `
System.Management.Automation.RuntimeDefinedParameterDictionary;
$paramDictionary.Add("FilePath", $FilePath);
$paramDictionary;
}
Begin {
if ( $FilePath.Value -eq $null -or $FilePath.Value -eq [string]::Empty) {
$FilePath.Value = $script:x;
} else {
$script:x = $FilePath.Value;
}
Write-Output ("(foo) FilePath: {0}" -f $FilePath.Value);
Write-Output ("(foo) RequireFilePath: {0}" -f $RequireFilePath.IsPresent);
Write-Output ("(foo) script:x: {0}" -f $script:x);
}
Process {
Write-Output ("(foo) InputObject: {0}" -f $InputObject);
}
End {
}
}
foo "filename2.txt" "zxcv";
When executed, I get this:
(foo) FilePath: zxcv
(foo) RequireFilePath: False
(foo) script:x: zxcv
(foo) InputObject: filename2.txt
I suppose my expectation was that the dynamic parameter was going to be position 0 and the static parameter was going to be position 1. Can anybody weigh in on this? Is it possible to have a dynamic parameter be defined to be at a postion lower than (earlier than) a static parameter?
After playing around with this a little bit, I discovered that adding the ValueFromRemainingArguments Parameter Attribute to the $InputObject parameter seems to get closest to the desired behavior; however, I am not entirely sure why.
Param (...
[Parameter(ParameterSetName="set1",
Position=1,
ValueFromPipeline=$true
ValueFromRemainingArguments=$true)]
$InputObject,
...)
Another approach that worked for me is to set the PositionalBinding argument of the CmdletBinding attribute to $False. Then if only the dynamic parameters have a Position set, then they get the positional values set correctly.
This of course assumes that you don't want any of the static parameters to have a position.