Here are my parameters of one of the nested modules in my main module:
Param(
[Parameter(Mandatory = $false, ParameterSetName = "set1", Position = 0, ValueFromPipeline = $true)][switch]$Get_BlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set2", Position = 0, ValueFromPipeline = $true)][switch]$Get_DriverBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set3", Position = 0, ValueFromPipeline = $true)][switch]$Make_AllowMSFT_WithBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set4", Position = 0, ValueFromPipeline = $true)][switch]$Deploy_LatestDriverBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set5", Position = 0, ValueFromPipeline = $true)][switch]$Set_AutoUpdateDriverBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set6", Position = 0, ValueFromPipeline = $true)][switch]$Prep_MSFTOnlyAudit,
[Parameter(Mandatory = $false, ParameterSetName = "set7", Position = 0, ValueFromPipeline = $true)][switch]$Make_PolicyFromAuditLogs,
[Parameter(Mandatory = $false, ParameterSetName = "set8", Position = 0, ValueFromPipeline = $true)][switch]$Make_LightPolicy,
[Parameter(Mandatory = $false, ParameterSetName = "set9", Position = 0, ValueFromPipeline = $true)][switch]$Make_SuppPolicy,
[Parameter(Mandatory = $false, ParameterSetName = "set10", Position = 0, ValueFromPipeline = $true)][switch]$Make_DefaultWindows_WithBlockRules,
[parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$ScanLocation,
[parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$SuppPolicyName,
[ValidatePattern('.*\.xml')][parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$PolicyPath,
[Parameter(Mandatory = $false, ParameterSetName = "set3")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Parameter(Mandatory = $false, ParameterSetName = "set8")]
[parameter(Mandatory = $false, ParameterSetName = "set9")]
[switch]$Deployit,
[Parameter(Mandatory = $false, ParameterSetName = "set8")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Parameter(Mandatory = $false, ParameterSetName = "set3")]
[switch]$TestMode,
[Parameter(Mandatory = $false, ParameterSetName = "set3")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Parameter(Mandatory = $false, ParameterSetName = "set8")]
[switch]$RequireEVSigners,
[Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Debugmode,
[ValidateSet([Levelz])]
[parameter(Mandatory = $false, ParameterSetName = "set7")]
[parameter(Mandatory = $false, ParameterSetName = "set9")]
[string]$Levels,
[ValidateSet([Fallbackz])]
[parameter(Mandatory = $false, ParameterSetName = "set7")]
[parameter(Mandatory = $false, ParameterSetName = "set9")]
[string[]]$Fallbacks,
[ValidateRange(1024KB, [int64]::MaxValue)]
[Parameter(Mandatory = $false, ParameterSetName = "set6")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Int64]$LogSize,
[Parameter(Mandatory = $false)][switch]$SkipVersionCheck
)
How can I prevent parameters that are not position 0 to appear in position 0?
I've tried adding other positions like 1,2 etc. to other parameters but that didn't work.
Only parameters with position 0 are the main ones, the rest only need to be used/suggested when a position 0 parameter is first selected by the user.
To clarify, I'm using this
Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete
and seeing parameters that don't belong to position 0 in the menu is just weird and I don't want them to appear there or be selectable.
the yellow underlines show parameters that don't make sense on their own if used in position 0.
Update, using Dynamic parameters, I did this but still the other yellow underlined parameters are showing up for position 0.
So maybe someone can post an answer showing how I can actually implement it in my function.
#requires -version 7.3.3
function New-WDACConfig {
[CmdletBinding(
DefaultParameterSetName = "set1",
HelpURI = "https://github.com/HotCakeX/Harden-Windows-Security/wiki/WDACConfig",
SupportsShouldProcess = $true,
PositionalBinding = $false,
ConfirmImpact = 'High'
)]
Param(
[Parameter(Mandatory = $false, ParameterSetName = "set1")][switch]$Get_BlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set2")][switch]$Get_DriverBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set3")][switch]$Make_AllowMSFT_WithBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set4")][switch]$Deploy_LatestDriverBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set5")][switch]$Set_AutoUpdateDriverBlockRules,
[Parameter(Mandatory = $false, ParameterSetName = "set6")][switch]$Prep_MSFTOnlyAudit,
[Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Make_PolicyFromAuditLogs,
[Parameter(Mandatory = $false, ParameterSetName = "set8")][switch]$Make_LightPolicy,
[Parameter(Mandatory = $false, ParameterSetName = "set9")][switch]$Make_SuppPolicy,
[Parameter(Mandatory = $false, ParameterSetName = "set10")][switch]$Make_DefaultWindows_WithBlockRules,
[parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$ScanLocation,
[parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)][string]$SuppPolicyName,
[ValidatePattern('.*\.xml')]
[parameter(Mandatory = $true, ParameterSetName = "set9", ValueFromPipelineByPropertyName = $true)]
[string]$PolicyPath,
[Parameter(Mandatory = $false, ParameterSetName = "set3")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Parameter(Mandatory = $false, ParameterSetName = "set8")]
[parameter(Mandatory = $false, ParameterSetName = "set9")]
[switch]$Deployit,
[Parameter(Mandatory = $false, ParameterSetName = "set8")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Parameter(Mandatory = $false, ParameterSetName = "set3")]
[switch]$TestMode,
[Parameter(Mandatory = $false, ParameterSetName = "set3")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Parameter(Mandatory = $false, ParameterSetName = "set8")]
[switch]$RequireEVSigners,
[Parameter(Mandatory = $false, ParameterSetName = "set7")][switch]$Debugmode,
[ValidateSet([Levelz])]
[parameter(Mandatory = $false, ParameterSetName = "set7")]
[parameter(Mandatory = $false, ParameterSetName = "set9")]
[string]$Levels,
[ValidateSet([Fallbackz])]
[parameter(Mandatory = $false, ParameterSetName = "set7")]
[parameter(Mandatory = $false, ParameterSetName = "set9")]
[string[]]$Fallbacks,
[ValidateRange(1024KB, [int64]::MaxValue)]
[Parameter(Mandatory = $false, ParameterSetName = "set6")]
[Parameter(Mandatory = $false, ParameterSetName = "set7")]
[Int64]$LogSize,
[Parameter(Mandatory = $false)][switch]$SkipVersionCheck
)
# parameter control for Make_AllowMSFT_WithBlockRules - Set3
DynamicParam {
if ($PSCmdlet.ParameterSetName -eq "Set3") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'RequireEVSigners', 'TestMode', 'Deployit') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set3" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [Int64], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
# parameter control for Prep_MSFTOnlyAudit - Set6
if ($PSCmdlet.ParameterSetName -eq "Set6") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'LogSize') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set6" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [Int64], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
# parameter control for Make_PolicyFromAuditLogs - Set7
if ($PSCmdlet.ParameterSetName -eq "Set7") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'Deployit', 'TestMode', 'RequireEVSigners') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set7" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
if ($PSCmdlet.ParameterSetName -eq "Set7") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'Fallbacks', 'Levels') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set7" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [string], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
if ($PSCmdlet.ParameterSetName -eq "Set7") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'LogSize') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set7" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [Int64], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
# parameter control for Make_LightPolicy - Set8
if ($PSCmdlet.ParameterSetName -eq "Set8") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'Deployit', 'TestMode', 'RequireEVSigners') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set8" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
# parameter control for Make_SuppPolicy - Set9
if ($PSCmdlet.ParameterSetName -eq "Set9") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'Fallbacks', 'Levels', 'Deployit', 'PolicyPath', 'SuppPolicyName', 'ScanLocation') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set9" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
if ($PSCmdlet.ParameterSetName -eq "Set9") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'Fallbacks', 'Levels', 'PolicyPath', 'SuppPolicyName', 'ScanLocation') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set9" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [string], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
if ($PSCmdlet.ParameterSetName -eq "Set9") {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in 'Deployit') {
[Parameter[]] $paramAttribute = [Parameter]@{ ParameterSetName = "Set9" }
$runtimeParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param, [switch], $paramAttribute)
$paramDictionary.Add($param, $runtimeParam)
}
return $paramDictionary
}
}
}
}
To complement briantist's helpful answer:
Context:
The concept of positional parameters doesn't apply to your use case, as it only applies when arguments (parameter values) are passed unnamed, i.e. not preceded by a target parameter name.
As an aside:
[switch]
parameters shouldn't be declared as positional (even though you can technically get away with it) - switch parameters are expected to be passed as named arguments, with their name alone implying their value ($true
), and their absence implying $false
.
Similarly, binding [switch]
parameters via the pipeline is highly unusual and users may not expect it. Switch parameters are usually understood to be a single flag per invocation, not one that may vary based on each pipeline input object.
Also, using _
in parameter names is unusual - consider using camel case instead; e.g., consider using $GetBlockRules
(-GetBlockRules
) instead of $Get_BlockRules
(-Get_BlockRules
).
Tab-completing parameter names always invariably offers you all of the statically declared parameters when completing the first argument, and - after having typed or completed at least one argument and tab-completing a new one after having typed only -
- may expand or shrink the set of parameters offered:
in the presence of multiple parameter sets, the set of static parameters offered may shrink, if the argument(s) specified so imply parameter set(s) that support only a subset of the static parameters.
in the presence of dynamic parameters, they may surface or disappear, depending on the conditions under which they are designed to become available.
Note that if you type a prefix of the name of a static parameter (e.g., -fo
), it will be offered for completion even if it doesn't belong to the parameter set(s) implied by arguments typed so far.
For discoverability and maintainability, it is best to make do with static parameters, as shown in briantist's answer, but it doesn't provide the step-by-step user guidance you're looking for.
Solution:
The only way to ensure that only the desired subset of parameters is shown when you initially type -
and tab-complete is to:
Declare only that subset as static parameters, putting each parameter in its own parameter set (as you already do).
-Verbose
from showing in the menu display; they show after the command-specific ones (they display is column-based, so read downward).Declare all other parameters as dynamic ones, surfacing them as appropriate based on what initial parameter was bound.
A minimal example:
[CmdletBinding(PositionalBinding = $false)]
param(
# The only parameters to show initially.
[Parameter(Mandatory, ParameterSetName = "set1")] [switch] $Get_BlockRules,
[Parameter(Mandatory, ParameterSetName = "set2")] [switch] $Get_DriverBlockRules,
[Parameter(Mandatory, ParameterSetName = "set3")] [switch] $Make_AllowMSFT_WithBlockRules
# All other parameters are declared *dynamically*
)
dynamicParam {
# Create a dynamic-parameters dictionary
$dict = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
# Define and add the -Deployit switch, which only belongs to
# parameter set "set3"
$paramName = 'Deployit'
$dict.Add(
$paramName,
[System.Management.Automation.RuntimeDefinedParameter]::new(
$paramName,
[switch],
[System.Management.Automation.ParameterAttribute] @{
ParameterSetName = 'set3'
}
)
)
# Define and add the -SkipVersionCheck switch, which belongs to *all*
# parameter sets.
$paramName = 'SkipVersionCheck'
$dict.Add(
$paramName,
[System.Management.Automation.RuntimeDefinedParameter]::new(
$paramName,
[switch],
[System.Management.Automation.ParameterAttribute] @{
ParameterSetName = '__allParameterSets'
}
)
)
# Return the dictionary
return $dict
}
process {
# Print all parameters that were bound.
$PSBoundParameters
}
Note:
The solution requires no Position
properties.
All static parameters - the only ones to show initially - are declared as Mandatory
, with no default parameter-set designation in the [CmdletBinding()]
attribute.
-SkipVersionCheck
from surfacing prematurely, and also prevents argument-less invocation.-SkipVersionCheck
is offered once any one of the initial, static parameters has been completed.
-Deployit
is only offered if the initially completed static parameter (switch) is -Make_AllowMSFT_WithBlockRules
, because like the latter it belongs to parameter set set3
only.