420

From what I know, PowerShell doesn't seem to have a built-in expression for the so-called ternary operator.

For example, in the C language, which supports the ternary operator, I could write something like:

<condition> ? <condition-is-true> : <condition-is-false>;

If that doesn't really exist in PowerShell, what would be the best way (i.e. easy to read and to maintain) to accomplish the same result?

7
  • 6
    Take a look at github.com/nightroman/PowerShellTraps/tree/master/Basic/… . If this is what you are looking for I can make it an answer. Commented Jul 10, 2015 at 13:36
  • 2
    It's a conditional operator or ternary if. It's not "the ternary operator" since all that means is a operator (any operator) that takes three arguments. Commented Jul 10, 2015 at 13:37
  • 11
    @Damien_The_Unbeliever That's technically true, but it's often called ternary operator. "Since this operator is often the only existing ternary operator in the language, it is sometimes simply referred to as "the ternary operator". In some languages, this operator is referred to as "the conditional operator." Ternary operation Commented Jul 10, 2015 at 13:47
  • Visual basic does not have a true ternary operator but considers the IF and IFF to be functionally equivelent. Commented Jul 10, 2015 at 13:55
  • 4
    The ternary operator was added to native PowerShell in version 7. Added an answer accordingly. Commented Jun 19, 2020 at 15:38

14 Answers 14

542
$result = If ($condition) {"true"} Else {"false"}

For use in or as an expression, not just an assignment, wrap it in $(), thus:

write-host  $(If ($condition) {"true"} Else {"false"}) 
Sign up to request clarification or add additional context in comments.

7 Comments

That works on the right side of an equals, but not quite as you'd expect a ternary operator - these fail: "a" + If ($condition) {"true"} Else {"false"} and "a" + (If ($condition) {"true"} Else {"false"}) This works (I'm not yet sure why): "a" + $(If ($condition) {"true"} Else {"false"})
@Lamarth - That works because the $() wrapper forces evaluation of the statement as an expression, thus returning either the true value of the false value, much like a ternary operator would be expected to. This is the closest you'll get in PowerShell AFAIK.
Unlike some of the other "non function" answers, this will also ensure that only one branch is executed.
"Everything else is incidental complexity and thus to be avoided." - opinion, not fact.
"Everything else is incidental complexity and thus to be avoided." - Great life advice.
|
212

Powershell 7, released 2020, introduced a ternary operator:

$message = (Test-Path $path) ? "Path exists" : "Path not found"

1 Comment

How the chuck am I only reading this now, after 5 desperate years alone in the dark, omg my life has a whole new meaning now ... wait a minit | answered Oct 7, 2019 at 14:19 | Powershell 7, released 2020 | ERM PICK ONE MAYBE?
92

The closest PowerShell construct I've been able to come up with to emulate that is:

@({'condition is false'},{'condition is true'})[$condition]

15 Comments

({true}, {false})[!$condition] is slightly better (perhaps): a) traditional order of true and false parts; b) $condition does not have to be just 0 or 1 or $false, $true. The operator ! converts it as needed. I.e. $condition can be, say, 42: !42 ~ $false ~ 0 ~ first expression.
Not quite identical, @RomanKuzmin. mjolinor's example returns a string. But the same string values from his example plugged into your expression returns a script block. :-(
By {true} and {false} I mean <true expression> and <false expression>, not script blocks. Sorry for being not accurate.
This will force eager-evaluation of left and right options: this is much different from a proper ternary operation.
@AlexKwitny Maybe your example is just for demonstration purposes, but it's not a great example. It's a really overwrought way of writing: $didUserChooseYes = $choice -eq 'Y'
|
36

Try powershell's switch statement as an alternative, especially for variable assignment - multiple lines, but readable.

Example,

$WinVer = switch ( Test-Path -Path "$Env:windir\SysWOW64" ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"

1 Comment

This may be slightly more verbose, but there are significant benefits over many of the other answers. In particular, there are no dependencies on external code, nor on functions being copied/pasted into a script or module.
28

Per this PowerShell blog post, you can create an alias to define a ?: operator:

set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse) 
{
   if (&$decider) { 
      &$ifTrue
   } else { 
      &$ifFalse 
   }
}

Use it like this:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})

3 Comments

There does not appear to be a reason that 'decider' is a scriptblock. If ?: is ever evaluated it will require the arguments are evaluated. Without a script block it would have a similar usage, eg. (?: ($quantity -le 10) {.9} {.75})
That is a cool feature but it can render your script as non-standard e.g. your script or portions of it may not port unless the alias definition is also ported with it. You are basically creating your own sub-language at this point. All of this can lead to increased cost of maintenance due to increased complexity and decreased readability.
@VanceMcCorkle Solid point. Custom terseness is worth its cost if useful often. But if the situation only occurs rarely, I'd stick with an "if" expression.
27

As of PowerShell version 7, the ternary operator is built into PowerShell.

1 -gt 2 ? "Yes" : "No"
# Returns "No"

1 -gt 2 ? 'Yes' : $null
# Get a $null response for false-y return value

3 Comments

Thanks. Any idea how to replace, for example, "No" statement with "null" or something?
@RuslanKorkin I just updated the answer with an example. You can use $null
23

I too, looked for a better answer, and while the solution in Edward's post is "ok", I came up with a far more natural solution in this blog post

Short and sweet:

# ---------------------------------------------------------------------------
# Name:   Invoke-Assignment
# Alias:  =
# Author: Garrett Serack (@FearTheCowboy)
# Desc:   Enables expressions like the C# operators: 
#         Ternary: 
#             <condition> ? <trueresult> : <falseresult> 
#             e.g. 
#                status = (age > 50) ? "old" : "young";
#         Null-Coalescing 
#             <value> ?? <value-if-value-is-null>
#             e.g.
#                name = GetName() ?? "No Name";
#             
# Ternary Usage:  
#         $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
#         $name = (get-name) ? "No Name" 
# ---------------------------------------------------------------------------

# returns the evaluated value of the parameter passed in, 
# executing it, if it is a scriptblock   
function eval($item) {
    if( $item -ne $null ) {
        if( $item -is "ScriptBlock" ) {
            return & $item
        }
        return $item
    }
    return $null
}

# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
    if( $args ) {
        # ternary
        if ($p = [array]::IndexOf($args,'?' )+1) {
            if (eval($args[0])) {
                return eval($args[$p])
            } 
            return eval($args[([array]::IndexOf($args,':',$p))+1]) 
        }

        # null-coalescing
        if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
            if ($result = eval($args[0])) {
                return $result
            } 
            return eval($args[$p])
        } 

        # neither ternary or null-coalescing, just a value  
        return eval($args[0])
    }
    return $null
}

# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."

Which makes it easy to do stuff like (more examples in blog post):

$message == ($age > 50) ? "Old Man" :"Young Dude" 

3 Comments

this is pretty awesome. I just don't like using = as the alias, because == is used in C# and a lot of other languages for equality checking. Having some experience in C# is pretty common among powershellers and that makes the resulting code a bit confusing. : would possibly be a better alias. Using it in conjuction with variable assignment =: might remind someone of the assignment used in Pascal, but does not have any immediate equivalent (that I know of) in VB.NET nor C#.
You could set this to whatever alias you'd like. : or ~ or whatever floats your boat. :D set-alias : Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment." # ternary $message =: ($age > 50) ? "Old Man" :"Young Dude" # null coalescing $message =: $foo ?? "foo was empty" `
I know, and I did, I was just commenting why I would use another alias.
10

Since a ternary operator is usually used when assigning value, it should return a value. This is the way that can work:

$var=@("value if false","value if true")[[byte](condition)]

Stupid, but working. Also this construction can be used to quickly turn an int into another value, just add array elements and specify an expression that returns 0-based non-negative values.

1 Comment

This will force eager-evaluation of left and right options: this is much different from a proper ternary operation.
6

I've recently improved (open PullRequest) the ternary conditional and null-coalescing operators in the PoweShell lib 'Pscx'
Pls have a look for my solution.


My github topic branch: UtilityModule_Invoke-Operators

Functions:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Aliases

Set-Alias :?:   Pscx\Invoke-Ternary                     -Description "PSCX alias"
Set-Alias ?:    Pscx\Invoke-TernaryAsPipe               -Description "PSCX alias"
Set-Alias :??   Pscx\Invoke-NullCoalescing              -Description "PSCX alias"
Set-Alias ??    Pscx\Invoke-NullCoalescingAsPipe        -Description "PSCX alias"

Usage

<condition_expression> |?: <true_expression> <false_expression>

<variable_expression> |?? <alternate_expression>

As expression you can pass:
$null, a literal, a variable, an 'external' expression ($b -eq 4) or a scriptblock {$b -eq 4}

If a variable in the variable expression is $null or not existing, the alternate expression is evaluated as output.

Comments

5

Since I have used this many times already and didn't see it listed here, I'll add my piece :

$var = @{$true="this is true";$false="this is false"}[1 -eq 1]

ugliest of all !

kinda source

5 Comments

This will force eager-evaluation of left and right options: this is much different from a proper ternary operation.
@user2864740 that's because there is no proper ternary operation in PS. This is a synthetic variant.
@ViggoLundén Lack of a proper ternary operator does not reduce the correctness of the comment. This "synthetic variant" has different behavior.
You're right, and after re-reading your comment I understand how it can make a real difference. Thanks.
5

PowerShell currently doesn't didn't have a native Inline If (or ternary If) but you could consider to use the custom cmdlet:

IIf <condition> <condition-is-true> <condition-is-false>

See: PowerShell inline If (IIf)

4 Comments

The linked post shows how to create an IIf function. However, there is no standard PowerShell IIf function/cmdlet.
@user2864740, So what? does that exclude the answer as a possible usable answer to the question: "what would be the best way (i.e. easy to read and to maintain) to accomplish the same result?" But that aside, the link refers to the IIf question (posted Sep 5 '14) that is very similar to this (duplicate?). The rest of the answers in the linked question could also be seen as added value (and where the don't, it is mainly because they are duplicates).
A little bit of explanation goes a long way, and this is why bare links are discouraged, as is not closing for duplicates if there is no additional added information. Consider that a very small explanation would significantly increase the quality of such a bare link answer: "Powershell does not support a direct 'ternary' (^but see other answers). However, it is possible to write a function such as (^using this method, or see other answers) ..", but that's not my job to add. Answers can be amend / updated / etc. as applicable.
@user2864740, thanks for your feedback, I have made adjustments to the answer accordingly.
3

If you're just looking for a syntactically simple way to assign/return a string or numeric based on a boolean condition, you can use the multiplication operator like this:

"Condition is "+("true"*$condition)+("false"*!$condition)
(12.34*$condition)+(56.78*!$condition)

If you're only ever interested in the result when something is true, you can just omit the false part entirely (or vice versa), e.g. a simple scoring system:

$isTall = $true
$isDark = $false
$isHandsome = $true

$score = (2*$isTall)+(4*$isDark)+(10*$isHandsome)
"Score = $score"
# or
# "Score = $((2*$isTall)+(4*$isDark)+(10*$isHandsome))"

Note that the boolean value should not be the leading term in the multiplication, i.e. $condition*"true" etc. won't work.

Comments

3

@MikeSchinkel's answer is almost correct for Powershell 5.1 (which is realistically what most machines have on them, PS7 is not useful if it's not installed), but it fails in a specific edge-case:

Powershell will coerce the result of the if expression into $null if it is an empty collection.

For example,

$test = $(if($true) {@()})
$test.GetType()

fails with You cannot call a method on a null-valued expression.

The correct trick is to use the aggressively-ugly powershell unary-comma operator.

$test = $(if($true) {,@()})
$test.GetType()

So, the full example code:

$trueValue = "foo","bar","baz","quux"
$falseValue = @()

($false, $true) | foreach-object {
  $condition = $_

  # meat is here
  $result = $(If ($condition) {,$trueValue} Else {,$falseValue}) 

  Write-Host "Length of result where condition is '$_': $($result.Count)"
}

yields

Length of result where condition is 'False': 0
Length of result where condition is 'True': 4

This appears to work for all possible cases of $trueValue and $falseValue, where they're an array, an object, or $null.

Same applies to switch-statements-as-expressions.

for($i = 0; $i -lt 7; $i++) {

  # meat is here
  $result = $(switch ($i) {
    0 {,$null}
    1 {,"example test"}
    2 {,@()}
    3 {,@('foo','bar','baz')}
    4 {,@{}}
    5 {,@{One=1; Two=2; Three=3}}
    6 {,[Collections.ArrayList]::new()}
    default {,"default"}
  })

  $resultDesc = if($result -eq $null) {
    '$null'
  } elseif($result -is [Collections.IEnumerable]) {
    #note: measure-object for hashtables always returns count of 1
    "[$($result.GetType())] with $(($result | measure-object).Count) members: $result"
  } else { 
    "[$($result.GetType())]: $result"
  }

  "$i = $resultDesc"
}

yields

0 = $null
1 = [string] with 1 members: example test
2 = [System.Object[]] with 0 members:
3 = [System.Object[]] with 3 members: foo bar baz
4 = [hashtable] with 1 members: System.Collections.Hashtable
5 = [hashtable] with 1 members: System.Collections.Hashtable
6 = [System.Collections.ArrayList] with 0 members:

So, the general rule:

if and switch can be used for expressions! It's great! But, you have to wrap them in $() and prepend all return values with , to prevent weird edge-cases in Powershell's "helpful" language.

2 Comments

Thank you for this, very underrated answer.
Good points, but note that you don't need the $(...) enclosure around the if and switch statements; not only that, it can have side effects.
1

Here's an alternative custom function approach:

function Test-TernaryOperatorCondition {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [bool]$ConditionResult
        ,
        [Parameter(Mandatory = $true, Position = 0)]
        [PSObject]$ValueIfTrue
        ,
        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateSet(':')]
        [char]$Colon
        ,
        [Parameter(Mandatory = $true, Position = 2)]
        [PSObject]$ValueIfFalse
    )
    process {
        if ($ConditionResult) {
            $ValueIfTrue
        }
        else {
            $ValueIfFalse
        }
    }
}
set-alias -Name '???' -Value 'Test-TernaryOperatorCondition'

Example

1 -eq 1 |??? 'match' : 'nomatch'
1 -eq 2 |??? 'match' : 'nomatch'

Differences Explained

  • Why is it 3 question marks instead of 1?
    • The ? character is already an alias for Where-Object.
    • ?? is used in other languages as a null coalescing operator, and I wanted to avoid confusion.
  • Why do we need the pipe before the command?
    • Since I'm utilising the pipeline to evaluate this, we still need this character to pipe the condition into our function
  • What happens if I pass in an array?
    • We get a result for each value; i.e. -2..2 |??? 'match' : 'nomatch' gives: match, match, nomatch, match, match (i.e. since any non-zero int evaluates to true; whilst zero evaluates to false).
    • If you don't want that, convert the array to a bool; ([bool](-2..2)) |??? 'match' : 'nomatch' (or simply: [bool](-2..2) |??? 'match' : 'nomatch')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.