2

I'm trying to write a server build script that includes Boolean parameters for various tasks, e.g. whether to install IIS. If the user does not specify this parameter one way or the other, I want the script to prompt for a decision, but for convenience and unattended execution I want the user to be able to explicitly choose to install IIS or NOT install IIS by setting the value to True or False on the command line and therefore avoid being prompted. My issue is that when I create a Boolean parameter, PowerShell automatically sets it to False, rather than leaving it null, if it wasn't specified on the command line. Here is the design that I THOUGHT would've worked:

param( [bool]$IIS ) if ($IIS -eq $null) { $InstallIIS = Read-Host "Do you want to install IIS? (Y/N)" if ($InstallIIS -eq "Y") {$IIS = $true} } if ($IIS) {Do stuff here} 

Any suggestions for how to achieve my desired outcome would be most appreciated. Then if this changes anything, what I'd REALLY like to do is leverage PSRemoting to accept these build decision parameters on the user's system host and then pass them to the targets as an ArgumentList, and I'm wondering if that will affect how these Booleans are handled. For example:

param ( [string[]]$Computers [bool]$IIS ) $Computers | Foreach-Object { Invoke-Command -ComputerName $_ -ArgumentList $IIS -ScriptBlock { param( [bool]$IIS ) if ($IIS -eq $null) { $InstallIIS = Read-Host "Do you want to install IIS? (Y/N)" if ($InstallIIS -eq "Y") {$IIS = $true} } if ($IIS) {Do stuff here} 

Ideas?

4 Answers 4

3

Well of course even though I Googled about this quite a bit before posting here, including discovering the [AllowNull()] parameter and finding that it did NOT help in my use case, I ended up finding the answer in the first Google search AFTER posting. This is what worked:

[nullable[bool]]$IIS 

My only gripe with that syntax is that running Get-Help against the script now returns shows this for the IIS parameter:

-IIS <Nullable`1> 

instead of:

-IIS <Boolean> 

But unless there's a more elegant way to achieve what I need, I think I can live with that by adding a useful description for that parameter as well as Examples.

Sign up to request clarification or add additional context in comments.

4 Comments

There is a more elegant and discoverable way, using parameter sets (see my answer).
I agree; two parameter sets is probably better probably for this use case. However [Nullable[Boolean]] is a handy way to get a tri-state boolean (in cases where two-state [Switch] is insufficient).
@Bill, suppose I had 3 variables for 3 build activities where I wanted a tri-state offering, and I wanted to support any combination of those states with any of the 3 variables. Can I do that with parameter sets? It seems if no parameters are specified, I get an error that the parameter set can't be determined, and if I specify one but not others, then PowerShell only seems to select that one parameter set and then does not handle the others properly. I'm open to using parameter sets, but I can't figure out how to achieve the flexibility I'm looking for with the number of possible activities.
Maybe. I would recommend creating a sample script that does nothing but echo output and experiment with multiple combinations. If you get stuck, you can post a question containing your sample code and ask for help from there.
2

The way to accomplish this is with Parameter Sets:

[CmdletBinding()] param ( [Parameter()] [string[]]$Computers , [Parameter(ParameterSetName = 'DoSomethingWithIIS', Mandatory = $true)] [bool]$IIS ) $Computers | Foreach-Object { Invoke-Command -ArgumentList $IIS -ScriptBlock { param( [bool]$IIS ) if ($PSCmdlet.ParameterSetName -ne 'DoSomethingWithIIS') { $InstallIIS = Read-Host "Do you want to install IIS? (Y/N)" if ($InstallIIS -eq "Y") {$IIS = $true} } if ($IIS) {Do stuff here} 

4 Comments

Cool! I'll experiment with this and see if I can extend it to accommodate the other optional build tasks I want to handle in this script. Many thanks!
This doesn't seem to work when I leave IIS unspecified on the command line. I get a "parameter set can't be resolved" error, which also becomes a problem if I try to add this to my additional build variables. I need to support build decisions like install IIS, partition and format non-system disks, etc. -- all of which should support being explicitly set to True or False, OR left with no upfront decision so that each target server will ask what to do for undecided tasks, in order to allow convenience for homogeneous builds and flexibility for heterogeneous builds.
@jphughan this wasn't part of your original question :) That scenario can be done with param sets but it doesn't scale well because you have to multiplex the sets, so the number of sets will grow very quickly. In this case you can use your nullables, you can create an enum that allows for true/false/undecided, and accept that type (bonus: the enum will implicitly support tab completion). You can create an "options" class with the enum as members, and then the func can accept that type. You can accept a string and use [ValidateSet()] but.. meh.
Fair point, I was trying to keep my example simple, obviously not realizing that my simplification made a material change. :) I like the nullable option I ended up finding despite the Get-Help quirk, but I'm going to look into the other suggestions you just made since every time I write a new script I've learned about some new capability that has become useful later as well. Thanks again!
0

Even though boolean operators handle $null, $False, '', "", and 0 the same, you can do an equality comparison to see which is which.

If ($Value -eq $Null) {} ElseIf ($Value -eq $False) {} ..etc.. 

In your situation, you want to use [Switch]$IIS. This will be $False by default, or $True if entered with the command a la Command -IIS, then you can handle it in your code like:

If ($IIS) {} 

Which will only be $True if entered at the command line with -IIS

3 Comments

I use the switch parameter, then the user wouldn't have a way to say, "Do not install IIS and do not ask me whether I want to." I don't want it to default to False if nothing was specified; I want it to default to "No decision has been made one way or the other yet."
If you want to use Param([bool].. then at the command line you have to do -Option $False or -Option $True
I know, and that extra typing I do tend to use the Switch parameter type instead in lots of my other scripts, but that only works when I only want to support 2 options, e.g. do this or don't. In this particular case I want the user to have 3 options: DO this without prompting, SKIP this without prompting, and ASK me what to do for each target.
0

Instead of using an equality test that's going to try to coerce the value to make the test work:

if ($IIS -eq $Null) 

Use -is to check the type directly:

PS C:\> $iis = $null PS C:\> $iis -is [bool] False 

3 Comments

I'm not trying to check the variable type. $IIS is always going to be Boolean IF it's defined. I just need to check whether it's been defined, and if so whether it's True or False. My problem had been that if I created it as a parameter to allow it to be specified on the command line, then if the user did NOT specify it on the command line, the parameter is automatically set to False rather than remaining null, which broke my intended design of having 3 possible behaviors rather than just 2. I wanted it to be available for use but remain null if it was unused.
Okay, then I've misunderstood the exact question. But I think in this case the the type test would still give you the answer to the question of whether the user had set a value for the parameter.
The problem with the type test is that if the variable is defined as a standard Boolean in the param list, then it will always be created as a Boolean on execution even if the user specifies nothing, because Boolean parameters left unspecified are automatically set to False, which didn't work for me. So your type test will always return True -- unless you null it first, but then you've wiped out the user's input, so that doesn't help either. I needed a way for an explicit denial (user-specified "False") to be handled differently from "no comment" (no input on command line), so to speak.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.