Other scripts with dynamic parameters have never given me any issues. The one thing that is different is this is the first time I am using a SecureString type on a static parameter.
If I call this paramenter, the Dynamic one will not appear. I'm not doing anything special to hide the dynamic parameter. It should always appear. The issue appears on both 5.1 and PowerShell core.
I would be interested to know if there is a solution to this problem.
The dynamic parameter looks at available system services and allows you to enter only those services as part of the parameter.
This is only an example to replicate what I'm experiencing. Calling the Name parameter and supplying the information has no effect on the Service Dynamic Parameter. As soon as Password Parameter is called, you cannot call the Service Parameter if you hadn't already.
function test-command {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Name,
[Parameter(Mandatory)]
[SecureString]
$Password
)
DynamicParam {
$ParameterName = 'Service'
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $true
$ParameterAttribute.HelpMessage = "Parameter Help."
$AttributeCollection.Add($ParameterAttribute)
$arrSet = (Get-Service).Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$AttributeAlias = New-Object System.Management.Automation.AliasAttribute('s', 'Serv')
$AttributeCollection.Add($AttributeAlias)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [array], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
return $RuntimeParameterDictionary
}
begin {
$Service = $PsBoundParameters[$ParameterName]
}
process {
$Service
}
end {
}
}
Yes, I could call Service Parameter first and then the remaining ones, but if there is an answer as to why it's behaving this way I'd certainly like to know. Plus, if someone else uses my script, they wouldn't automatically know to do this. Is this a bug or am I doing something wrong?
Related
[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|...]
A Module Variable
$ModVar = #{Value = Key}
Errors when using static Param -Name, as a key reference.
PS> help Test-Function
InvalidOperation:
Line |
214 | $ModItem = $ModVar[$Name]
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Index operation failed; the array index evaluated to null.
I believe this is because the Param -Name does not exist until execution time. The function otherwise works correctly when used, this error only happens when using Get-Help.
Is there a methodology for getting around the error?
I have tried:
If (!$Name) {$Name = "A_Key_in_ModVar"}
In an attempt to supply a dummy value when it doesn't detect a value in -Name, but this causes the function to always overwrite the value of -Name, even if the parameter -Name is provided with an explicit value from the terminal.
Gist Example: GitHub to Get-KeyAndPeeleSchoolName
Code Snippet (full example above):
DynamicParam {
$ParameterName = 'School'
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
Write-Error $Name
$attSet = $Table[$Name]
Write-Error $attSet
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($attSet)
$ValidateSetAttribute.ErrorMessage = "Value must be $attSet"
$AttributeCollection.Add($ValidateSetAttribute)
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.ParameterSetName = "ParamSet1"
$AttributeCollection.Add($ParameterAttribute)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
$RuntimeParameterDictionary
}
Edit 1: Other issues I noticed.
Values for -Name that contains spaces prevent the Dynamic Param block containing -School from working.
Also when using ArgumentCompleter on -Names, the possible values with complex punction fail to completely autocomplete. This is probably because of the matching behavior, but any suggestions for how to make this behave would be great.
Also using " " quotes around a parameter value also prevent the argument completer from working, any suggestions for this would also be great.
Wrapping the [RuntimeDefinedParameterDictionary] object and accompanying constructs in an IF statement resolved the issue. -Name does not exist pre-binding when -Name is not passed a value explicitly. It is also not available when Get-Help runs to analyze the makeup of the Function.
To resolve this, check for the existence of the parameter being referenced within the DynamicParam block to control its execution.
DynamicParam {
if ($Name) { #If $Name exist create Dynamic Param.
$ParameterName = 'School'
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$attSet = $Table[$Name]
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($attSet)
$ValidateSetAttribute.ErrorMessage = "Value must be $attSet"
$AttributeCollection.Add($ValidateSetAttribute)
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.ParameterSetName = "ParamSet1"
$AttributeCollection.Add($ParameterAttribute)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
$RuntimeParameterDictionary
}
}
The parameter doesn't seem to be "set" as a parameter at all. Validate set doesn't work. Neither does autocomplete. Typing in the parameter name doesn't work either.
I know I did dynamic parameters before. But this time, I'm missing something. Just can't figure out what it is.
Function Add-Control() {
DynamicParam {
$ParamAttribute = New-Object Parameter
$ParamAttribute.Mandatory = $true
$ParamAttribute.ParameterSetName = '__AllParameterSets'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$AttributeCollection.Add($ParamAttribute)
$controlTypes = #("TextBox", "Label", "DataGrid")
$AttributeCollection.Add((New-Object ValidateSet($controlTypes)))
$RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Type', [string], $AttributeCollection)
$RuntimeParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$RuntimeParamDictionary.Add('Type', $RuntimeParam)
return $RuntimeParamDictionary
}
Process {
Write-Host ($PSBoundParameters['Type'])
}
}
Add-Control -Type "Test"
# $null
Not sure if this is a stupid mistake, but I surely feel that way. I was missing
[CmdletBinding()]
Param()
Both validate set and autocomplete work now.
Hopefully this helps others.
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
)
...
}
I found this method of dynamically updating the validate set members of a parameter.
This let's me do something like this:
Function MyFunction([ValidateSet("Placeholder")]$Param1) { "$Param1" }
Update-ValidateSet -Command (Get-Command MyFunction) -ParameterName "Param1" -NewSet #("red","green")
But is there any way of adding a validation attribute that was not already present? Specifically, I have a set of functions that would benefit greatly by having dynamically created validate sets. However, as the link above makes clear, this is a hack, and may break in the future. So I don't want to put a placeholder ValidateSet, in case it needs to be removed in the future. Essentially, I'd like to do something like this:
Function MyFunction($Param1) { "Param1" }
Add-ValidateSet -Command (Get-Command MyFunction) -ParameterName "Param1" -NewSet #("red", "green")
This way, if it ever does break, it would be easier to remove the breaking code. But I have not been able to get this to work. I've tried doing this:
$parameter = (Get-Command MyFunction).Parameters["P1"]
$set = "Red","Orange","Yellow","Green","Blue","Indigo","Violet"
$Attribute = new-object System.Management.Automation.ValidateSetAttribute $Set
$ValidValuesField = [System.Management.Automation.ValidateSetAttribute].GetField("validValues", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
$ValidValuesField.SetValue($Attribute, [string[]]$Set)
$parameter.Attributes.Add($Attribute)
But it does not work.
(Get-Command MyFunction).Parameters["P1"].Attributes
shows that the ValidateSet has been added, but tab completion does not work. Comparing it with the results of using the Update-ValidateSet function, it appears that the difference is that the attribute should also appear under
(Get-Command MyFunction).ParameterSets[0].Parameters[0].Attributes
However, that is a ReadOnlyCollection, so I don't seem to be able to add it there. Am I just barking up the wrong tree here? Is this not possible to do?
You accomplish this using dynamic parameters. The dynamic parameters will be evaluated as your command is typed into the command window.
This is from about_Functions_Advanced_Parameters
function Get-Sample {
[CmdletBinding()]
Param ([String]$Name, [String]$Path)
DynamicParam
{
if ($path -match ".*HKLM.*:")
{
$attributes = new-object 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
}
}