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
Related
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
}
[CmdletBinding()]
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$subscription,
[Parameter(Mandatory=$false)]
[string]
$applicationId,
[Parameter(Mandatory=$false)]
[string]
$clientSecret,
[Parameter(Mandatory=$true,Position=1)]
[string]
$resourceGroupName,
[Parameter(Mandatory=$true,Position=2)]
[string]
$apimServiceName,
[Parameter(Mandatory=$true,Position=3)]
[string]
$policyfilePath,
[Parameter(Mandatory=$true,Position=4)]
[ValidateSet('global','product','api','operation', IgnoreCase = $true)]
[string]
$scope='global',
[Parameter(Mandatory=$true,Position=5)]
[string]
$apiIdOrProductId
#THIS IS THE PARAMETER WHICH I WANT TO MAKE MANDATORY IF VALUE OF PREVIOUS PARAMETER $scope = "OPEARTION"
#[Parameter(Mandatory=$false)]
#[string]
#$operationname
)
DynamicParam {
if ($scope -eq "OPERATION" -or $scope -eq "operation") {
#create a new ParameterAttribute Object
Write-Host "Called test"
Write-Host $scope
$operationAttribute = New-Object System.Management.Automation.ParameterAttribute
$operationAttribute.Mandatory = $true
$operationAttribute.Position = 6
#create an attributecollection object for the attribute we just created.
$attributeCollection = new-object System.Collections.ObjectModel.Collection[System.Attribute]
#add our custom attribute
$attributeCollection.Add($operationAttribute)
#add our paramater specifying the attribute collection
$operationName = New-Object System.Management.Automation.RuntimeDefinedParameter('operationname', [String], $attributeCollection)
#expose the name of our parameter
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add('operationname', $operationName)
return $paramDictionary
}
}
I cannot get the dynamic parameter to work which is dependent on a previous parameter "scope". It just skips over when I run the PS script. Please advice what I'm doing wrong?
Your code only works if you pass an argument to your -scope parameter directly on the command line.
If you let PowerShell prompt you for a -scope argument, based on the parameter's Mandatory property, your code cannot work, because the dynamicparam block runs before such automatic prompts.
Since you're assigning a default value to $scope, 'global' (which doesn't make sense in combination with Mandatory=$true), one option is to simply remove the Mandatory property:
This will default to 'global' in the absence of a (possibly positional) -scope argument, in which case you know that the additional parameter isn't required.
If an argument is specified, then your dynamicparam block will work as intended.
However, this also requires you to place the mandatory $apiIdOrProductId parameter declaration before $scope (swap Position values 4 and 5)
Based on the title, there is at least one way to have a dynamic param offer a ValidateSet for that dynamic parameter based on the value of a previously entered regular parameter.
In this case the regular parameter could be a folder.
But you would have to enter it without quotes.
For instance:
Function Get-SubFolderDynamically {
[CmdletBinding()]
param($path)
dynamicparam
{
Write-Verbose "DynamicParam triggered. PSBoundParameters `$path is currently: $($PSBoundParameters['path'])" -Verbose
#Parameter Definition
$attributes = New-Object -Type System.Management.Automation.ParameterAttribute
$attributes.Mandatory = $true
$attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
#AcceptedValues for Parameter Definition
#Note: Only Works if path is entered in the command line without quotes
$subFolders = (Get-ChildItem $path).Name
$attributeCollection.add((New-Object System.Management.Automation.ValidateSetAttribute($subFolders)))
#ParameterDictionary
$dynParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("subFolder", [string], $attributeCollection)
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("subFolder", $dynParam)
return $paramDictionary
}
}
#RUNIT - Create a Folder with subFolders to test.
Get-SubFolderDynamically -path C:\PathWithoutQuotes -subFolder [TAB|TAB|...]
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.
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.
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
}