Open imfrancisd opened 8 years ago
Not really sure what you mean by "3 states" - switch parameters by their nature have only 2 states. They are either on or off. Passing an explicit boolean is only used in special cases, like for -Confirm switches to override the default ConfirmPreference:
Set-Something -Confirm:$false
Of course, you can create your own parameters with unlimited numbers of arguments. Depending on your use case this may or not be more useful or intuitive than a switch parameter. I definitely would not choose to use a string parameter where there are only two choices - this seems un-intuitive and runs counter to the behavior of most commands.
EDIT: It's also worth noting that the syntax above using ":" to bind arguments to parameters is not unique to switch parameters. You can use this to pass a value to any parameter.
Usually, switch parameters are viewed as having 2 states (true or false), but you can also look at them as having 3 states (missing, present/true, explicitly false).
For example, Get-ChildItem treats the -Directory switch as having only 2 states (true or false), which is how most switch parameters are treated in many functions.
#list directories
Get-ChildItem -Directory
#list all
Get-ChildItem
Get-ChildItem -Directory:$false
But Get-ChildItem could have also treated the -Directory switch as having 3 states (missing, present/true, explicitly false).
It could have done something like this:
#list all
Get-ChildItem
#list directories
Get-ChildItem -Directory
#could have made this list files
Get-ChildItem -Directory:$false
If Get-ChildItem treated the -Directory switch parameter as having 3 states (missing, present/true, explicitly false), then the -File switch would not be needed, and weird cases like this would not be possible:
#list nothing
Get-ChildItem -Directory -File
But I don't know if designing a function to treat a switch parameter as having three states is an acceptable practice in PowerShell.
So, given your example with the -Directory switch, what would be different about the explicitly false scenario. This is essentially the same as missing/false, so I don't think I fully understand how you would treat this differently in a function. Also, why is there not a 4th state for explicitly true?
As far as the Get-ChildItem example with both the -File and -Directory parameters, I think this is probably a bug and I wouldn't be surprised if someone on this project is aware of it. I'm not really sure how these are implemented, but from the looks of it, they are dynamic parameters since they aren't returned by the Get-Help.
It would only return files.
#this
Get-ChildItem -Directory:$false
#could have been similar to this
Get-ChildItem -Attributes !D
#and this
cmd /c dir /a-d
I'm not sure if you can tell from inside the function if a switch parameter is present or explicitly true to get a fourth state.
At least I don't think you can without looking at the command that called the function.
But this is much more explicit, and you immediately know what it does:
Get-ChildItem -File
And that's the whole point of PowerShell. Once you start down the path described above, you're losing a lot of what we gain with PowerShell - readability and consistency of behavior. One of the biggest problems with existing shells is that each application is a world unto itself with its' own behavior. This increases the learning curve dramatically. Clearly with any managed technology there is always a bit of "domain-specific" problems and jargon, but it's not a good idea to design our user experience around them.
And yes, you can check if a switch parameter was explicitly bound:
function Test-SwitchParam
{
[CmdletBinding()]
param ([switch]$Switch)
$PSBoundParameters.ContainsKey("Switch")
}
Yes, Get-ChildItem -File is easier than Get-ChildItem -Directory:$false.
You can test if the parameter is bound, but you cannot tell the difference between -Switch and -Switch:$true.
function Test-SwitchParam
{
[CmdletBinding()]
param ([switch]$Switch)
$PSBoundParameters.ContainsKey("Switch")
$Switch.ToBool()
}
Test-SwitchParam -Switch
Test-SwitchParam -Switch:$true
Switches should be treated as booleans, period. Trying to make a third "state" is a mistake that will just confuse users at some point. (Splatting comes to mind as a common way to screw with that logic.)
Then I think that should be placed in the Best Practices guide (if it is I couldn't find it).
The reason I ask is that if you have a function like Get-ChildItem that already has a default behavior (show files and directories) and you want to add the ability to show only files, or show only directories, using a switch with 3 states becomes very tempting.
To put this another way, how would you have added the ability to show only files or to show only directories to Get-ChildItem. Would you have used switch parameters like they do now, or use something else?
Perhaps ItemType or PathType as New-Item and Test-Path do, that is a parameter backed by ValidateSet or an Enumeration.
On 23 August 2016 at 17:29, Francis de la Cerna notifications@github.com wrote:
Then I think that should be placed in the Best Practices guide (if it is I couldn't find it).
The reason I ask is that if you have a function like Get-ChildItem that already has a default behavior (show files and directories) and you want to add the ability to show only files, or show only directories, using a switch with 3 states becomes very tempting.
To put this another way, how would you have added the ability to show only files or to show only directories to Get-ChildItem. Would you have used switch parameters like they do now, or use something else?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/69#issuecomment-241792039, or mute the thread https://github.com/notifications/unsubscribe-auth/AMO6Dl3bD0zJf52oXOi7Q9G2-opSbmYaks5qix_XgaJpZM4JqqEF .
I would have probably gone with an enumerated type with a default value. (Or a ValidateSet, which amounts to the same thing). Something like this:
function Get-ChildItem
{
param (
[ValidateSet("All", "File", "Directory")]
[string] $ItemType = "All"
)
}
I suspect that their decision to have -File
and -Directory
switches was driven by wanting to make dir
/ ls
feel more familiar to users of different shells.
Ok, thanks. :)
Yeah, if it's not just a boolean True/False, you'd want an enum. If we don't have a best practices about switches, I suppose it wouldn't hurt to add. We ought to have best practices on a few parameter types: switches, credentials, paths ...
As an additional point, we've had some problems with functions that behaved differently than expected when people tried to pass through a switch statement:
function Test-One {
[CmdletBinding()]
param(
[switch]$Force
)
Test-Two -Force:$Force
}
In this situation, simply not passing -Force
to Test-One
will result in calling Test-Two -force:$false
Treating it as trinary makes it substantially harder to reuse
There exists one approach to mimic 3 state behavior - I needed this kind of system, because despite of default parameters , I need to re-define default parameters based on environment variable.
$PSBoundParameters.ContainsKey("Switch")
can be used to query whether user provided specific flag from command line. If not provided - then can figure out it's default values. (If you're using alias - then ContainsKey
need to have actual parameter name, not it's alias)
(Referred information: https://stackoverflow.com/a/56809664/2338477)
But after that use switch normally, compare to $True / $False
as necessary.
There is only one catch to this - if you have batch wrapper over .ps1 script - then that batch file must not use -File
switch - provide whole .ps1 file path instead.
For example:
test.bat:
@echo off
set args=%*
set args=%args:"='"%
powershell -executionpolicy bypass "%~dpn0.ps1" %args%
(Referred information: https://stackoverflow.com/a/40799651/2338477)
Extra char replacement " => '"
is done so powershell script can accept string with quotes (e.g. -Param ""
)
To be clear. The guidance here is simple:
As implied by the design, and documented in the official about_ docs
I'm tempted to create a function that does different things when the switch parameter is missing, true, and false.
Using the 3 states cuts down on the number of switch parameters, and sometimes makes the function easier to understand, but I don't know how widely this practice is accepted by the community.
Example
For an example of when a 2-state switch parameter is misleading, look at Get-ChildItem -File -Directory.
So far so good, but look at this.
Using 3 states for this is much simpler for the script writer. It may be easier for the user too.
Question
So, would it be okay to use 3 states for switch parameters like the -Directory switch for Get-ChildItem?