Does a DynamicParameter Switch not read like a Static Parameter [switch]? - powershell

Hopefully the Title is clear enough but, I am having some trouble understanding on how to evaluate against a DynamicParameter Switch, compared to a Static (type casted) switch.
In the following code block, there are 2 switches that become available only if the other 2 parameters are not null, and/or, are not empty:
Add
Remove
Function Test-DynamParam {
Param (
# Input Parameters
[Parameter(Mandatory = $false,
HelpMessage='Enter. Workflow. Name.')]
[Alias('OMB','MailBox')]
[string]$Workflow,
[Parameter(Mandatory = $false)]
[Alias('EDIPI','DisplayName')]
[string[]]$UserName
)
DynamicParam {
if ($Workflow -ne $null -and $UserName -ne $null) {
$parameterAttribute = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = "AddingMembers"
Mandatory = $false
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
$dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'Add', [switch], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('Add', $dynParam1)
$parameterAttribute1 = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = "RemovingMembers"
Mandatory = $false
}
$attributeCollection1 = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection1.Add($parameterAttribute1)
$dynParam11 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'Remove', [switch], $attributeCollection1
)
$paramDictionary.Add('Remove', $dynParam11)
return $paramDictionary
}
}
Process {
$Add.IsPresent
}
}
Running:
Test-DynamParam -Workflow 'd' -UserName 'a' -Add
returns empty.
Unfortunately, $Add.IsPresent is not evaluated to any boolean value regardless if the switch is present or not. Yet in this function it is (which makes sense):
Function Test-StaticParam {
Param (
[switch]$Add
)
$Add.IsPresent
}
Running:
Test-StaticParam -Add
returns True.
Question
How can I evaluate against dynamic parameter chosen?

Use the $PSBoundParameters automatic variable:
Process {
$PSBoundParameters['Add'].IsPresent
}

Related

Unable to evaluate against a specific value

I am having trouble thinking of the correct logic on how to go about this. I have a function that accepts two types of switches:
-Add
-Remove
Function Test-Bool {
Param (
# Input Parameters
[Parameter(Mandatory = $false,
HelpMessage='Enter. Workflow. Name.')]
[Alias('OMB','MailBox')]
[string]$Workflow,
[Parameter(Mandatory = $false)]
[Alias('EDIPI','DisplayName')]
[string[]]$UserName
)
DynamicParam {
if ($Workflow -ne $null -and $UserName -ne $null) {
$parameterAttribute = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = "AddingMembers"
Mandatory = $false
}
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($parameterAttribute)
$dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'Add', [switch], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add('Add', $dynParam1)
$parameterAttribute1 = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = "RemovingMembers"
Mandatory = $false
}
$attributeCollection1 = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection1.Add($parameterAttribute1)
$dynParam11 = [System.Management.Automation.RuntimeDefinedParameter]::new(
'Remove', [switch], $attributeCollection1
)
$paramDictionary.Add('Remove', $dynParam11)
return $paramDictionary
}
}
Begin {
$ABool = {
'Add Block'
$Bool = Read-Host -Prompt 'Adding or Removing Member(s)'
if ($Bool.ToLower() -like 'a*') {
$true
}
else {
Break;
}
}
$RBool = {
'Remove Block'
$Bool = Read-Host -Prompt 'Adding or Removing Member(s)'
if ($Bool.ToLower() -like 'r*') {
$true
}
else {
Break;
}
}
if ($PSBoundParameters['Add'].IsPresent) { [bool]$Add = $true }
elseif ($PSBoundParameters['Remove'].IsPresent) { [bool]$Remove = $true }
elseif (-not$PSBoundParameters['Add'].IsPresent) { $Add = & $ABool }
elseif (-not$PSBoundParameters['Remove'].IsPresent) { $Remove = & $RBool }
}
Process {
if ($Add) {
"Add was selected"
}
if ($Remove) {
"Remove was selected"
}
}
}
I can run it several ways:
Test-Bool -Workflow spg -UserName a -Add - works
Test-Bool -Workflow spg -UserName a -Remove - works
Test-Bool -Workflow spg
Should ask to remove or add: Typing Add, works.
Typing Remove, does not work.
Which makes sense, because elseif (-not$PSBoundParameters['Add'].IsPresent) { $Add = & $ABool } is evaluated first and when asked, Remove is typed in which throws it into the else block making it Break/Exit.
QUESTION:
What can I change to make $Remove be evaluated to true and vise-versa when Add is typed (to make $Add True)?
I honestly confused myself so much that I just decided to post here instead of attempting to figure this out on my own.
Use Parameter Sets instead:
Function Test-Bool {
[CmdletBinding(DefaultParameterSetName = 'Undecided')]
Param (
# Input Parameters
[Parameter(Mandatory = $false,
HelpMessage='Enter. Workflow. Name.')]
[Alias('OMB','MailBox')]
[string]$Workflow,
[Parameter(Mandatory = $false)]
[Alias('EDIPI','DisplayName')]
[string[]]$UserName,
[Parameter(Mandatory = $true, ParameterSetName = 'Add')]
[switch]$Add,
[Parameter(Mandatory = $true, ParameterSetName = 'Remove')]
[switch]$Remove
)
begin {
$action = if($PSCmdlet.ParameterSetName -eq 'Undecided'){
$answer = Read-Host -Prompt 'Adding or Removing Member(s)?'
if($answer -like 'a*'){
'Add'
}
elseif($answer -like 'r*') {
'Remove'
}
else {
throw 'Invalid option provided'
}
}
else {
$PSCmdlet.ParameterSetName
}
# $action now contains either 'Add' or 'Remove'
$actionBlock = #{
'Add' = { <# code to add user to workflow #> }
'Remove' = { <# code to remove user from workflow #> }
}[$action]
}
process {
# if/else statements no longer needed, $actionBlock contains the correct scriptblock
. $actionBlock
}
}
If a user doesn't specify either switch, the parameter set name will be Undecided, and the user will be prompted in the begin block - otherwise we simply use the parameter set name associated with the specified switch.
Since the two switch parameters belong to separate parameter sets, the user can no longer pick both.

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.

Validate parameter value using another parameter value

I'm trying to validate whether a path exists before running a function.
There is no default for the folder path, but the file name default should be template.csv. Is there a way, through the ValidateScript attribute, to validate a parameter value based on another parameter value?
The below code returns the error that the variable $TemplateDir has not been set. I'm also not entirely sure if it would test for the default file name value.
function get-Template {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[ValidateScript({Test-Path $_})]
[string]$TemplateDir,
[Parameter(Mandatory = $false)]
[ValidateScript({Test-Path ($TemplateDir + "\" + $_)})]
[string]$TemplateFile = "template.csv"
)
...
}
Any advice?
You can set up a dynamic parameter with the DynamicParam block, that depends on the value of another mandatory parameter:
function Get-FilePath
{
Param (
[Parameter(Mandatory = $true, Position = 0)]
[ValidateScript({Test-Path $_})]
[string]$TemplateDir
)
DynamicParam {
# Set up parameter attribute
$fileParamAttribute = New-Object System.Management.Automation.ParameterAttribute
$fileParamAttribute.Position = 3
$fileParamAttribute.Mandatory = $false
$fileParamAttribute.HelpMessage = "Please supply a file name"
# Set up ValidateSet param with actual file name values
$fileValidateParam = New-Object System.Management.Automation.ValidateSetAttribute #(Get-ChildItem $TemplateDir -File |Select-Object -ExpandProperty Name)
# Add the parameter attributes to an attribute collection
$attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($fileParamAttribute)
$attributeCollection.Add($fileValidateParam)
# Create the actual $TemplateFile parameter
$fileParam = New-Object System.Management.Automation.RuntimeDefinedParameter('TemplateFile', [string], $attributeCollection)
# Push the parameter(s) into a parameter dictionary
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add('TemplateFile', $fileParam)
# Return the dictionary
return $paramDictionary
}
begin{
# Check if a value was supplied, otherwise set it
if(-not $PSBoundParameters.ContainsKey('TemplateFile'))
{
$TemplateFile = 'template.csv'
}
$myPath = Join-Path $TemplateDir -ChildPath $TemplateFile
}
end {
return $myPath
}
}
This will also give you automatic tab completion for the arguments to -TemplateFile
You can read more about DynamicParam with Get-Help about_Functions_Advanced_Parameters

Create switch parameter using DynamicParam

Is it possible to create a [switch]-like parameter using DynamicParam? I know that I can just create a Boolean parameter, but in this case I will be forced to initialize its value like -BooParam $true, but I want to just type -BooParam. Why I need it - I would like to expose one switch parameter using Tab only if second is defined.
Yes, it is possible using DynamicParam:
function Test-Function
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false)]
[switch]$flag1
)
DynamicParam
{
if ($flag1)
{
$flag2 = New-Object System.Management.Automation.ParameterAttribute
$flag2.Mandatory = $false
$flag2.HelpMessage = "Only available if flag1 is set"
$attributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($flag2)
$flag2param = New-Object System.Management.Automation.RuntimeDefinedParameter('flag2', [switch], $attributeCollection)
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add('flag2', $flag2param)
return $paramDictionary
}
}
}
Parameter sets might be a better (simpler) way to achieve your goal.
function Do-Something {
[CmdletBinding(DefaultParameterSetName='none')]
Param(
...
[Parameter(ParameterSetName='set1', Mandatory=$true)]$Foo,
[Parameter(ParameterSetName='set1')][Switch]$Bar
)
...
}

Can I make a parameter set depend on the value of another parameter?

Let's say I have a function like:
function Authenticate
{
param
(
[ValidateSet('WindowsAuthentication','UsernameAndPassword')][string] $AuthenticationType,
[Parameter(ParameterSetName='Set1')][string] $Username,
[Parameter(ParameterSetName='Set1')][string] $Password
)
..
}
And I would like to make the parameter set to be mandatory when $AuthenticationType = 'UsernameAndPassword' but also so that it cannot be used if $AuthenticationType = 'WindowsAuthentication'.
It this even possible in PowerShell?
Using the link from Tim Ferrill's answer, I created the following function to help create dynamic parameters:
function New-DynamicParameter
{
[CmdletBinding(DefaultParameterSetName = 'Core')]
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)][string] $Name,
[Parameter(Mandatory = $true, ParameterSetName = 'Core')][Parameter(Mandatory = $true, ParameterSetName = 'ValidateSet')][type] $Type,
[Parameter(Mandatory = $false)][string] $ParameterSetName = '__AllParameterSets',
[Parameter(Mandatory = $false)][bool] $Mandatory = $false,
[Parameter(Mandatory = $false)][int] $Position,
[Parameter(Mandatory = $false)][bool] $ValueFromPipelineByPropertyName = $false,
[Parameter(Mandatory = $false)][string] $HelpMessage,
[Parameter(Mandatory = $true, ParameterSetName = 'ValidateSet')][string[]] $ValidateSet,
[Parameter(Mandatory = $false, ParameterSetName = 'ValidateSet')][bool] $IgnoreCase = $true
)
process
{
# Define Parameter Attributes
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.ParameterSetName = $ParameterSetName
$ParameterAttribute.Mandatory = $Mandatory
$ParameterAttribute.Position = $Position
$ParameterAttribute.ValueFromPipelineByPropertyName = $ValueFromPipelineByPropertyName
$ParameterAttribute.HelpMessage = $HelpMessage
# Define Parameter Validation Options if ValidateSet set was used
if ($PSCmdlet.ParameterSetName -eq 'ValidateSet')
{
$ParameterValidateSet = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $ValidateSet -Strict (!$IgnoreCase)
}
# Add Parameter Attributes and ValidateSet to an Attribute Collection
$AttributeCollection = New-Object Collections.ObjectModel.Collection[System.Attribute]
$AttributeCollection.Add($ParameterAttribute)
$AttributeCollection.Add($ParameterValidateSet)
# Add parameter to parameter list
$Parameter = New-Object System.Management.Automation.RuntimeDefinedParameter -ArgumentList #($Name, $Type, $AttributeCollection)
# Expose parameter to the namespace
$ParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$ParameterDictionary.Add($Name, $Parameter)
return $ParameterDictionary
}
}
And solved my particular problem in the following way:
function Authenticate
{
param
(
[ValidateSet('WindowsAuthentication','UsernameAndPassword')][string] $AuthenticationType,
)
DynamicParam
{
if ($AuthenticationType -eq 'UsernameAndPassword')
{
New-DynamicParameter Username [string] -Mandatory $true
New-DynamicParameter Password [string] -Mandatory $true
}
}
...
}
It became unneeded to have a parameter set when using Dynamic Parameter so I removed the parameter set.
You can do this using DynamicParam. I saw a decent post on this recently here.
DynamicParam {
if ($AuthenticationType -eq 'UsernameAndPassword') {
#create ParameterAttribute Objects for the username and password
$unAttribute = New-Object System.Management.Automation.ParameterAttribute
$unAttribute.Mandatory = $true
$unAttribute.HelpMessage = "Please enter your username:"
$pwAttribute = New-Object System.Management.Automation.ParameterAttribute
$pwAttribute.Mandatory = $true
$pwAttribute.HelpMessage = "Please enter a password:"
#create an attributecollection object for the attributes we just created.
$attributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
#add our custom attributes
$attributeCollection.Add($unAttribute)
$attributeCollection.Add($pwAttribute)
#add our paramater specifying the attribute collection
$unParam = New-Object System.Management.Automation.RuntimeDefinedParameter('username', [string], $attributeCollection)
$pwParam = New-Object System.Management.Automation.RuntimeDefinedParameter('password', [string], $attributeCollection)
#expose the name of our parameter
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add('username', $unParam)
$paramDictionary.Add('password', $pwParam)
return $paramDictionary
}
}
Process {
$PSBoundParameters.username
$PSBoundParameters.password
}