363

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?

andiDo
  • 309
  • 4
  • 11
mguassa
  • 4,051
  • 2
  • 14
  • 19
  • 6
    Take a look at https://github.com/nightroman/PowerShellTraps/tree/master/Basic/Missing-ternary-operator . If this is what you are looking for I can make it an answer. – Roman Kuzmin Jul 10 '15 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. – Damien_The_Unbeliever Jul 10 '15 at 13:37
  • 9
    @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](https://en.wikipedia.org/wiki/Ternary_operation) – mguassa Jul 10 '15 at 13:47
  • Visual basic does not have a true ternary operator but considers the IF and IFF to be functionally equivelent. – Matt Jul 10 '15 at 13:55
  • @Matt That's incorrect. The `IIF` *function* will always evaluate both operands. The `If` statement will not - see https://stackoverflow.com/questions/1220411/vb-net-iif-both-sides-are-evaluated-what-situations-should-i-watch-ou (newer VB.NET versions added a ternary expression: `If (x, y, z)`) – user2864740 Jan 15 '18 at 02:32
  • I feel like powershell should allow you to use whatever .NET language you want. Then there won't be missing operator questions, because they'll all be there. – Brain2000 Dec 09 '18 at 01:07
  • 4
    The ternary operator was added to native PowerShell in version 7. Added an answer accordingly. –  Jun 19 '20 at 15:38

14 Answers14

490
$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"}) 
MikeSchinkel
  • 4,947
  • 4
  • 38
  • 46
fbehrens
  • 6,115
  • 2
  • 19
  • 22
  • 3
    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 Nov 30 '16 at 01:26
  • 20
    @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. – KeithS Mar 08 '17 at 14:51
  • 6
    Unlike some of the other "non function" answers, this will *also* ensure that only one branch is executed. – user2864740 Jan 11 '18 at 21:40
  • 3
    "Everything else is incidental complexity and thus to be avoided." - opinion, not fact. – TimTheEnchanter Nov 02 '20 at 14:47
  • 6
    "Everything else is incidental complexity and thus to be avoided." - Great life advice. – Max Cascone Mar 11 '21 at 16:50
150

Powershell 7 has it.

PS C:\Users\js> 0 ? 'yes' : 'no'
no
PS C:\Users\js> 1 ? 'yes' : 'no'
yes
zett42
  • 25,437
  • 3
  • 35
  • 72
js2010
  • 23,033
  • 6
  • 64
  • 66
84

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

@({'condition is false'},{'condition is true'})[$condition]
Maximilian Burszley
  • 18,243
  • 4
  • 34
  • 63
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • 25
    `({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. – Roman Kuzmin Jul 10 '15 at 15:46
  • 2
    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. :-( – Michael Sorens Jul 28 '15 at 16:10
  • 6
    By `{true}` and `{false}` I mean `` and ``, not script blocks. Sorry for being not accurate. – Roman Kuzmin Jul 28 '15 at 16:44
  • 1
    Thanks for the clarification, @RomanKuzmin--now I see the value in your suggestion. – Michael Sorens Sep 16 '15 at 16:30
  • As per blog post from 2102 - http://www.kongsli.net/2012/02/23/powershell-another-alternative-to-ternary-operator/ –  May 11 '16 at 10:12
  • 18
    This will force eager-evaluation of left and right options: this is much different from a proper ternary operation. – user2864740 Jan 11 '18 at 21:39
  • 1
    Or `('condition is false','condition is true')[$condition]` – js2010 Jun 12 '19 at 14:07
  • Or `[[bool]$condition]` for objects. – js2010 Jun 12 '19 at 14:57
  • The comments here confused me, while my goal was close. Notice no script block. Here's an obvious sample that can be used: `$didUserChooseYes = ($true, $false)[!($choice -eq 'Y')]` – Alex Kwitny Jun 21 '19 at 17:10
  • 1
    @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'` – JLRishe Jul 01 '19 at 03:34
  • @JLRishe, yes it is a practical demonstration. The first comment combined with the answer is confusing when you're trying to get a working product. Many people working with PowerShell are not developers, they're SysAdmins and pseudo code doesn't translate to raw syntax they can plug/play. – Alex Kwitny Jul 01 '19 at 14:45
  • @AlexKwitny Ok, it's just not really an example of a case where you would want to use this kind of thing. I think a better real-world example would be something like: `@('Not old enough to drive', 'Old enough to drive')[$age -ge 17]` – JLRishe Jul 01 '19 at 15:01
  • This is super useful for testing results like this `({true}, {false})[!$?]`. However, I was not able to get it to work with: `({'ok'}, {Write-Host -fore red 'fail!'})[!$?]`. Is this not possible? – not2qubit Dec 24 '20 at 04:02
30

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"
nudl
  • 451
  • 5
  • 6
  • 2
    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. – bshacklett Dec 14 '18 at 14:50
26

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})
Community
  • 1
  • 1
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • 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})` – user2864740 Jan 15 '18 at 03:11
  • 3
    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. – Vance McCorkle May 04 '20 at 22:22
  • 1
    @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. – Edward Brey May 05 '20 at 10:51
24

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
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" 
Garrett Serack
  • 943
  • 7
  • 17
  • 3
    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#. – nohwnd Jan 14 '17 at 10:25
  • 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"` ` – Garrett Serack Jan 17 '17 at 16:58
  • I know, and I did, I was just commenting why I would use another alias. – nohwnd Jan 18 '17 at 14:09
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.

Vesper
  • 18,599
  • 6
  • 39
  • 61
  • 7
    This will force eager-evaluation of left and right options: this is much different from a proper ternary operation. – user2864740 Jan 11 '18 at 21:39
9

The ternary operator in PowerShell was introduced with the PowerShell version7.0.

[Condition] ? (output if True) : (output if False)

Example 01

$a = 5; $b = 6
($a -gt $b) ? "True" : "False"

Output

False

Example 02

($a -gt $b) ? ("$a is greater than $b") : ("$a is less than $b")

Output

5 is less than 6

more information https://www.tutorialspoint.com/how-ternary-operator-in-powershell-works

kris
  • 392
  • 4
  • 16
6

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

sodawillow
  • 12,497
  • 4
  • 34
  • 44
  • 5
    This will force eager-evaluation of left and right options: this is much different from a proper ternary operation. – user2864740 Jan 11 '18 at 21:39
  • @user2864740 that's because there is no proper ternary operation in PS. This is a synthetic variant. – Viggo Lundén Apr 11 '19 at 08:55
  • 2
    @ViggoLundén Lack of a proper ternary operator does not reduce the correctness of the comment. This "synthetic variant" **has different behavior**. – user2864740 Apr 11 '19 at 18:25
  • You're right, and after re-reading your comment I understand how it can make a real difference. Thanks. – sodawillow Apr 11 '19 at 21:23
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.

andiDo
  • 309
  • 4
  • 11
4

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)

iRon
  • 20,463
  • 10
  • 53
  • 79
  • The linked post shows how to *create* an `IIf` function. However, there is no standard PowerShell `IIf` function/cmdlet. – user2864740 Jan 11 '18 at 09:28
  • 1
    @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). – iRon Jan 11 '18 at 13:32
  • 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 Jan 11 '18 at 19:18
  • @user2864740, thanks for your feedback, I have made adjustments to the answer accordingly. – iRon Jan 16 '18 at 08:01
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.

nmbell
  • 451
  • 3
  • 7
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')
JohnLBevan
  • 22,735
  • 13
  • 96
  • 178