What I'm looking for is the ability to use a parameter to pass a variable, yet be able to act similar to a switch. Essentially...
No parameter:
PS>FunctionName
Or, parameter used like a switch where it will utilize a default value
PS>FunctionName -Parameter
Or, parameter used with a specified override to the default value
PS>FunctionName -Parameter 123
The idea is that I have a need for a function to accept multiple switch style inputs to say enable/disable, use/don't-use a particular feature. To that end a switch parameter works fine. However, many of the parameters are not only a true/false but also a variable, and I want to be able to provide the user a way to override the default value. A second parameter could be specified and required with dynamic parameters, to which the the input might look as follows...
PS>FunctionName -Parameter -ParameterOverride 123
But, this is clunky at best and considering the function I'm presently trying to work with has need for 8 parameters of this style it's down right ugly to use from the users perspective.
It's not exactly what you're after, but you could potentially utilise a [ValidateSet()]
to make this easier to work with.
It's not exactly what you're after, but you could potentially utilise a
[ValidateSet()]
to make this easier to work with.
How exactly would you employ a ValidateSet to solve this problem?
Hmm. Actually, that's only really a good solution if you have a specific set of acceptable values. If you have others... hmm. Yeah, okay, not sure. Depends on the exact requirements -- you can, for example, check $PSBoundParameters
to verify if the value was actually passed deliberately or comes from some default parameter value you have set... and you can of course restrict input types.
I was thinking something along these lines:
[CmdletBinding()]
param(
# ...
[Parameter()]
[ValidateSet('Value1','Value2','Value3','Default')]
$Parameter
# ...
)
if ($Parameter -eq 'Default') { $Parameter -eq 'Value2' }
What I've tried so far with limited success was this...
Function test {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$false)]
[ValidateRange(1,25)]
[Int]
$OptionalNumber = 1
)
if($PSBoundParameters.keys -contains "OptionalNumber"){
Write-Output "The optional number parameter is present"
Write-Output "The Optional Number is: $OptionalNumber"
}else{Write-Output "The optional number parameter was not specified"}
}
that works with running
PS>test
or running
PS>test -OptionalNumber 4
but fails when running
PS>test -OptionalNumber
I've tried (and failed) with all kinds of combinations of [ValidationScript()], [AllowNull()], [Nullable[int]] in an attempt to modify the parameter requirements, but every time it complains that there must be a value passed in when the "-OptionalNumber" parameter is present.
Yeah. The default value will be used whenever the parameter is not specified at all.
Is there an important distinction here where you need the parameter to be specified for some reason, even though the default value will be used anyway?
for what it's worth, there was some discussion on this already on StackOverflow.com
https://stackoverflow.com/questions/60459934/use-function-parameter-as-both-a-variable-and-switch
https://stackoverflow.com/questions/58838941/powershell-special-switch-parameter
Stack OverflowI have the powershell function below Function Test { Param ( [Parameter()] [string]$Text = "default text" ) Write-Host "Text : $($Text)" } And I w...
Stack OverflowI have the powershell function below Function Test { Param ( [Parameter()] [string]$Text = "default text" ) Write-Host "Text : $($Text)" } And I w...
Yeah. The default value will be used whenever the parameter is not specified at all.
Is there an important distinction here where you need the parameter to be specified for some reason, even though the default value will be used anyway?
Basically, I want the presence (or lack thereof) of the parameter to be triggered like a switch parameter. If it's not there, just ignore the fact that it even has a default value set. However, if the parameter is present, then use whatever value the user specifies, and if no value is specified, use the default value.
This isn't how I'm needing to use it (my case is a bit too much to explain easily), but imagine that you have a parameter that you call "$Log". You use that parameter to say that you want your function to enable a logging/transcript function. You have a default location already baked in... be it a specific or relative path.. but you want to give them the option to specify their own path.
So this just runs the function without logging...
PS>ExampleFunction
This would run with logging to the default location...
PS>ExampleFunction -Log
And this would run with logging to a specific location...
PS>ExampleFunction -Log C:\Temp\log.txt
Again.. it is just an example of how it could be used. My use case is different but similar.
Let me illustrate the proposal with a real-world example of such a parameter from the GNU sed
Unix utility, whose -i
option (for creating a backup of the input file) works as follows:
If -i
isn't specified at all, no backup is created.
If _only_ -i
is specified, a backup file with _default extension .bak
_ is created.
If the user wishes to specify a different extension, they can _pass an optional value_ to -i
; e.g.,
-i.org
.
For this to work _syntactically_ in Powershell, the optional value, if specified, would have to be _attached to the parameter name_, with :
as the separator (_update_: though there may be whitespace between the :
and the argument):
-MakeBackup:".org"
This is how it already works for [switch]
parameters, and this attachment is necessary to avoid ambiguity between optional parameter values and subsequent _positional_ arguments.
Actually there is no need for "attachment". This works fine:
function foo { param( [switch] $zork) $zork}
PS> foo
IsPresent
---------
False
PS> foo -zork
IsPresent
---------
True
PS> foo -zork: $false
IsPresent
---------
False
It's the ':' on the end of the switch that tells parser that the next argument binds to the switch parameter so syntactically the desired behavior could be implemented. However if this is generalized, how will the script distinguish between absent, present and present with a value? You'd have to encapsulate the value in a "magic" class containing the value along with the present
flag. (This is more or less what which does.) This seems unnecessarily complicated.
Every time I've wished for this type of parameter, I realized I was trying to fit too much into a single command. The best way to handle this currently is one of these:
Have a switch that just enables the behavior, this parameter would belong to AllParameterSets and also BehaviorNameSet
. Have a separate optional parameter to specify the argument which only belongs to BehaviorNameSet
Have a switch that enables the behavior, this parameter only belongs to BehaviorNameDefaultSet
. Have a separate mandatory parameter to specify the argument which only belongs to BehaviorNameExplicitArgSet
. Default parameter set would be AllParameterSets to make both optional
Both of those solutions are heavy on parameter sets. If you find that you're running into trouble related to the number of sets you have, consider splitting the functionality into separate commands. While this would probably be a common request, I think that ultimately the lack of this syntax leads to better design (for a PowerShell command anyway, not disparaging monolith CLI executables).
I also agree with @bpayette, usage of this type of parameter in a resilient way would be pretty verbose (if even possible).
Good point about the whitespace, @bpayette: It's not the _whitespace_, it is the :
that constitutes the attachment, and that :
is all that's needed to unambiguously designate the argument as belonging to the parameter - as is already the case with and necessary for [switch]
parameters, generalizing the logic of which is the gist of this proposal.
However if this is generalized, how will the script distinguish between absent, present and present with a value?
In order: -not $PSBoundParameters.ContainsKey('zork')
, default value, bound value.
No magic needed at all.
@SeeminglyScience:
Have a switch that just enables the behavior (...). Have a separate optional parameter
Avoiding the ceremony and awkwardness of having to say -PleaseDo
and
-IfyouDoThenWith WithValue
instead of just -PleaseDo:WithValue
(with -PleaseDo
with a default value _typically_ being enough) is what this proposal is about.
I get that this is amounts to a _new concept_ (though not _technically_, given that [switch]
already works this way), which is always challenging:
Users, if they're even aware of :
as a parameter/argument separator at all, will have to understand that in the case of such optional-argument parameters they'll _have to_ use it.
:
may be a blessing in this case: start publicizing it in the context of this new feature, and recommend that it _only_ be used for that.We'll have to come up with a way of representing optional-argument parameters in the syntax diagrams (and, of course, implementation-wise in the parameter _declaration_, which could be as simple as [Parameter(ValueOptional)]
).
I personally think it's worth doing, but I get that it introduces additional complexity, and that it would require a focused effort to publicize and properly document the new feature.
@SeeminglyScience:
Have a switch that just enables the behavior (...). Have a separate optional parameter
Avoiding the ceremony and awkwardness of having to say
-PleaseDo
and
-IfyouDoThenWith WithValue
instead of just-PleaseDo:WithValue
(with-PleaseDo
with a default value _typically_ being enough) is what this proposal is about.
But how many scenarios are there where a better design wouldn't be Please-Do WithValue
?
Also personally I'd say -PleaseDo:WithValue
is more awkward. I know that syntax already exists, but thankfully the use cases for it are pretty infrequent. Objectively less ceremony, but the awkwardness of either syntax is subjective.
Also personally I'd say
-PleaseDo:WithValue
is more _awkward_.
... where a better design wouldn't bePlease-Do WithValue
?
Assuming we're comparing -PleaseDo -IfYouDoThenWith WithValue
with -PleaseDo:WithValue
(Were you referring to -PleaseDo WithValue
? That doesn't satisfy the requirements, because you then couldn't specify -PleaseDo
alone, which would be the _typical_ case):
If we're talking about which syntax is inconveniently verbose, involves conceptual duplication, and requires you to remember _two_ parameter names, I think we have a clear winner.
If we're talking potential obscurity and the challenge of making the feature widely known and lodging it in users' minds, I would agree that the proposed feature is potentially problematic - hence my suggestion to publicize and document it properly, should it get implemented.
Undoubtedly, if implemented, this feature will only be used infrequently, but I personally see its utility.
Also personally I'd say
-PleaseDo:WithValue
is more _awkward_.
... where a better design wouldn't bePlease-Do WithValue
?Assuming we're comparing
-PleaseDo -IfYouDoThenWith WithValue
with-PleaseDo:WithValue
(Were you referring to-PleaseDo WithValue
? That doesn't satisfy the requirements, because you then couldn't specify-PleaseDo
alone, which would be the _typical_ case):
I wasn't comparing any of them, I was saying if you have a parameter that invokes a different action it should be a different command.
If we're talking about which syntax is inconveniently verbose, involves conceptual duplication, and requires you to remember _two_ parameter names, I think we have a clear winner.
If we're talking potential obscurity and the challenge of making the feature widely known and lodging it in users' minds, I would agree that the proposed feature is potentially problematic - hence my suggestion to publicize and document it properly, should it get implemented.
More specifically I'm saying the syntax of specifying a value to a switch parameter is more awkward than the alternatives (in my opinion). In regards to having to remember two parameter names, I'd much rather the parameters be named based on the type of value they are. For example, a Backup
switch and a BackupPath
string parameter makes usage pretty clear.
Also now that I think about it, what I see most often is:
PleaseDo
PleaseDoPath
PleaseDo
then the default path (or w/e value) is used. If you specify PleaseDoPath
then PleaseDo
is implied. If you specify both, then the behavior is the same as just specifying PleaseDoPath
.That way you don't need complicated parameter sets, different syntax, duplication, and you can still name your parameter in a way that telegraphs what sort of value it expects.
function Test-Function {
[CmdletBinding()]
param(
[Parameter()]
[switch] $PleaseDo,
[Parameter()]
[string] $PleaseDoPath = "$pwd\here.bak"
)
end {
$PleaseDo = $PSBoundParameters.ContainsKey('PleaseDoPath') -or $PleaseDo.IsPresent
if ($PleaseDo) {
Write-Host "Should do with value: $PleaseDoPath"
}
}
}
Test-Function -PleaseDo
Test-Function -PleaseDoPath something
Test-Function -PleaseDo -PleaseDoPath something
That way you don't need complicated parameter sets
If this were to be implemented, it would _simplify_ parameter declaration (fewer parameters, no one-implies-the-other logic, a single Parameter
attribute such as ValueOptional
).
Yes, your example is shows the best approach with the current features.
I'd much rather the parameters be named based on the type of value they are.
I agree that that's conceptually clearer.
The only way that the proposed feature would match that is if the ValueOptional
property accepted an - optional :) - value that allowed you to _name_ the optional argument (e.g., ValueOptional='Path'
) (or perhaps a separate property, ValueOptionalLabel='Path'
:), with the resulting syntax diagram then showing something like -PleaseDo[:(<string Path>)]
instead of -PleaseDo[:<string>]
On the whole, I can definitely see that implementing and publicizing the new feature would be a nontrivial effort, and that the relative rarity of the real-world need for it could be an impediment to its adoption and understandability.
I see the emoji, @SeeminglyScience - which part of my previous comment is confusing? I'm happy to clarify.
To offer a hint up front: I can see both sides of the debate, and I hope that the arguments I've presented allow everyone to draw their own conclusions.
I know it's called confused, but it can convey a few different emotions. In this specific context, it means "I disagree for the reasons above".
As I mentioned before, I think it's important to make it easy to tell at a glance if there is consensus in a thread. Plus as you know, it wouldn't be the first time that the committee has mistaken the side I've taken in a debate.
Plus as you know, it wouldn't be the first time that the committee has mistaken the side I've taken in a debate.
Amen to that (/cc @SteveL-MSFT; I know there's nuance there, but the summary definitely misses the mark).
can convey a few different emotions
In a technical debate, we're not looking for a _range_ of emotions, we want to communicate _precisely_ and _avoid ambiguity_.
That can be achieved in one of two ways:
Preferably, argue your points concisely and precisely.
If you must - because you don't want to expend more energy on a given debate and / or there's nothing more to say - boil it down to a _binary_ (dichotomy): yay ( 馃憤) or nay ( 馃憥) - as you've done before.
Plus as you know, it wouldn't be the first time that the committee has mistaken the side I've taken in a debate.
Amen to that (/cc @SteveL-MSFT; I know there's nuance there, but the summary definitely misses the mark).
To be clear I don't necessarily blame them, generally speaking. They are (I would guess) pretty strapped for time. I would bet that it's a pretty big ask just to get them to read an RFC, much less a really long thread.
Especially when we're both involved, that thread is going to be long. And hey, I love a good debate, and I love talking about PowerShell, I don't mind our long threads. But the more we go back and forth, the less likely it is that anyone other than us are going to read any of it. Should it be that way? Ideally no, but it's a reality. If we want our feedback to count, we gotta keep these threads shorter. Since we aren't the decision makers, we need to lay out our points and dip, we don't necessarily need to come to an understanding.
Both excellent points, @SeeminglyScience.
I'm increasingly making use of hiding outdated / resolved comments in order to keep threads short(er), and I often update the initial post with salient information discovered later and / or try to summarize a debate succinctly in a later comment.
But the overall length is still a problem, and I wish there were some protocol for arriving at an agreed-upon final form of a proposal or concise summary of a debate- short of authoring an RFC, which often isn't appropriate (yet) and, it seems, is where ideas go to die - that doesn't require future readers to read the entire history.
Having a shared, perhaps even documented understanding of how to use emoji would help too, though, at least based on the limited set of standard emoji offered by the site, I guess it's now down to the question whether to use 馃憥or 馃槙to signal one's final yay-or-nay stance (even though I bristled at it initially, I think the former is preferable).
function foo { param( [switch] $zork) $zork}
I would love for this to work as you describe if only switch were to accept more than just $true/$false. If the switch parameter were to allow decoration as any variable type beyond Boolean, this would solve the problem I have at hand....
PS > function foo { param( [switch] $zork) $zork}
PS > foo
IsPresent
---------
False
PS > foo -zork
IsPresent
---------
True
PS > foo -zork: 4
foo : Cannot process argument transformation on parameter 'zork'. Cannot convert the "4" value of type "System.Int32" to type "System.Management.Automation.SwitchParameter".
At line:1 char:12
+ foo -zork: 4
+ ~
+ CategoryInfo : InvalidData: (:) [foo], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,foo
The irony being that switch as an alternative to using endless if/else statements handles anything you can thing of to throw at it. But as a parameter can only handle $true/$false.
@chaydock, note that the [switch]
parameter _type_ and the switch
statement only happen to share the same name, but are otherwise unrelated.
I think we've already established the desired behavior and hinted at possible implementations.
Implementing this feature is definitely possible, but I don't think it's useful to further discuss implementation details until a decision is made on _if_ this feature should be supported at all, which, of course, may be negative - or may never come.
Most helpful comment
Both excellent points, @SeeminglyScience.
I'm increasingly making use of hiding outdated / resolved comments in order to keep threads short(er), and I often update the initial post with salient information discovered later and / or try to summarize a debate succinctly in a later comment.
But the overall length is still a problem, and I wish there were some protocol for arriving at an agreed-upon final form of a proposal or concise summary of a debate- short of authoring an RFC, which often isn't appropriate (yet) and, it seems, is where ideas go to die - that doesn't require future readers to read the entire history.
Having a shared, perhaps even documented understanding of how to use emoji would help too, though, at least based on the limited set of standard emoji offered by the site, I guess it's now down to the question whether to use 馃憥or 馃槙to signal one's final yay-or-nay stance (even though I bristled at it initially, I think the former is preferable).