Everything you wanted to know about the if statement

Like many other languages, PowerShell has statements for conditionally executing code in your scripts. One of those statements is the If statement. Today we will take a deep dive into one of the most fundamental commands in PowerShell.

Note

The original version of this article appeared on the blog written by @KevinMarquette. The PowerShell team thanks Kevin for sharing this content with us. Please check out his blog at PowerShellExplained.com.

Conditional execution

Your scripts often need to make decisions and perform different logic based on those decisions. This is what I mean by conditional execution. You have one statement or value to evaluate, then execute a different section of code based on that evaluation. This is exactly what the if statement does.

The if statement

Here is a basic example of the if statement:

$condition = $true
if ( $condition )
{
    Write-Output "The condition was true"
}

The first thing the if statement does is evaluate the expression in parentheses. If it evaluates to $true, then it executes the scriptblock in the braces. If the value was $false, then it would skip over that scriptblock.

In the previous example, the if statement was just evaluating the $condition variable. It was $true and would have executed the Write-Output command inside the scriptblock.

In some languages, you can place a single line of code after the if statement and it gets executed. That isn't the case in PowerShell. You must provide a full scriptblock with braces for it to work correctly.

Comparison operators

The most common use of the if statement for is comparing two items with each other. PowerShell has special operators for different comparison scenarios. When you use a comparison operator, the value on the left-hand side is compared to the value on the right-hand side.

-eq for equality

The -eq does an equality check between two values to make sure they're equal to each other.

$value = Get-MysteryValue
if ( 5 -eq $value )
{
    # do something
}

In this example, I'm taking a known value of 5 and comparing it to my $value to see if they match.

One possible use case is to check the status of a value before you take an action on it. You could get a service and check that the status was running before you called Restart-Service on it.

It's common in other languages like C# to use == for equality (ex: 5 == $value) but that doesn't work with PowerShell. Another common mistake that people make is to use the equals sign (ex: 5 = $value) that is reserved for assigning values to variables. By placing your known value on the left, it makes that mistake more awkward to make.

This operator (and others) has a few variations.

  • -eq case-insensitive equality
  • -ieq case-insensitive equality
  • -ceq case-sensitive equality

-ne not equal

Many operators have a related operator that is checking for the opposite result. -ne verifies that the values don't equal each other.

if ( 5 -ne $value )
{
    # do something
}

Use this to make sure that the action only executes if the value isn't 5. A good use-cases where would be to check if a service was in the running state before you try to start it.

Variations:

  • -ne case-insensitive not equal
  • -ine case-insensitive not equal
  • -cne case-sensitive not equal

These are inverse variations of -eq. I'll group these types together when I list variations for other operators.

-gt -ge -lt -le for greater than or less than

These operators are used when checking to see if a value is larger or smaller than another value. The -gt -ge -lt -le stand for GreaterThan, GreaterThanOrEqual, LessThan, and LessThanOrEqual.

if ( $value -gt 5 )
{
    # do something
}

Variations:

  • -gt greater than
  • -igt greater than, case-insensitive
  • -cgt greater than, case-sensitive
  • -ge greater than or equal
  • -ige greater than or equal, case-insensitive
  • -cge greater than or equal, case-sensitive
  • -lt less than
  • -ilt less than, case-insensitive
  • -clt less than, case-sensitive
  • -le less than or equal
  • -ile less than or equal, case-insensitive
  • -cle less than or equal, case-sensitive

I don't know why you would use case-sensitive and insensitive options for these operators.

-like wildcard matches

PowerShell has its own wildcard-based pattern matching syntax and you can use it with the -like operator. These wildcard patterns are fairly basic.

  • ? matches any single character
  • * matches any number of characters
$value = 'S-ATX-SQL01'
if ( $value -like 'S-*-SQL??')
{
    # do something
}

It's important to point out that the pattern matches the whole string. If you need to match something in the middle of the string, you need to place the * on both ends of the string.

$value = 'S-ATX-SQL02'
if ( $value -like '*SQL*')
{
    # do something
}

Variations:

  • -like case-insensitive wildcard
  • -ilike case-insensitive wildcard
  • -clike case-sensitive wildcard
  • -notlike case-insensitive wildcard not matched
  • -inotlike case-insensitive wildcard not matched
  • -cnotlike case-sensitive wildcard not matched

-match regular expression

The -match operator allows you to check a string for a regular-expression-based match. Use this when the wildcard patterns aren't flexible enough for you.

$value = 'S-ATX-SQL01'
if ( $value -match 'S-\w\w\w-SQL\d\d')
{
    # do something
}

A regex pattern matches anywhere in the string by default. So you can specify a substring that you want matched like this:

$value = 'S-ATX-SQL01'
if ( $value -match 'SQL')
{
    # do something
}

Regex is a complex language of its own and worth looking into. I talk more about -match and the many ways to use regex in another article.

Variations:

  • -match case-insensitive regex
  • -imatch case-insensitive regex
  • -cmatch case-sensitive regex
  • -notmatch case-insensitive regex not matched
  • -inotmatch case-insensitive regex not matched
  • -cnotmatch case-sensitive regex not matched

-is of type

You can check a value's type with the -is operator.

if ( $value -is [string] )
{
    # do something
}

You may use this if you're working with classes or accepting various objects over the pipeline. You could have either a service or a service name as your input. Then check to see if you have a service and fetch the service if you only have the name.

if ( $Service -isnot [System.ServiceProcess.ServiceController] )
{
    $Service = Get-Service -Name $Service
}

Variations:

  • -is of type
  • -isnot not of type

Collection operators

When you use the previous operators with a single value, the result is $true or $false. This is handled slightly differently when working with a collection. Each item in the collection gets evaluated and the operator returns every value that evaluates to $true.

PS> 1,2,3,4 -eq 3
3

This still works correctly in an if statement. So a value is returned by your operator, then the whole statement is $true.

$array = 1..6
if ( $array -gt 3 )
{
    # do something
}

There's one small trap hiding in the details here that I need to point out. When using the -ne operator this way, it's easy to mistakenly look at the logic backwards. Using -ne with a collection returns $true if any item in the collection doesn't match your value.

PS> 1,2,3 -ne 4
1
2
3

This may look like a clever trick, but we have operators -contains and -in that handle this more efficiently. And -notcontains does what you expect.

-contains

The -contains operator checks the collection for your value. As soon as it finds a match, it returns $true.

$array = 1..6
if ( $array -contains 3 )
{
    # do something
}

This is the preferred way to see if a collection contains your value. Using Where-Object (or -eq) walks the entire list every time and is significantly slower.

Variations:

  • -contains case-insensitive match
  • -icontains case-insensitive match
  • -ccontains case-sensitive match
  • -notcontains case-insensitive not matched
  • -inotcontains case-insensitive not matched
  • -cnotcontains case-sensitive not matched

-in

The -in operator is just like the -contains operator except the collection is on the right-hand side.

$array = 1..6
if ( 3 -in $array )
{
    # do something
}

Variations:

  • -in case-insensitive match
  • -iin case-insensitive match
  • -cin case-sensitive match
  • -notin case-insensitive not matched
  • -inotin case-insensitive not matched
  • -cnotin case-sensitive not matched

Logical operators

Logical operators are used to invert or combine other expressions.

-not

The -not operator flips an expression from $false to $true or from $true to $false. Here is an example where we want to perform an action when Test-Path is $false.

if ( -not ( Test-Path -Path $path ) )

Most of the operators we talked about do have a variation where you do not need to use the -not operator. But there are still times it is useful.

! operator

You can use ! as an alias for -not.

if ( -not $value ){}
if ( !$value ){}

You may see ! used more by people that come from another languages like C#. I prefer to type it out because I find it hard to see when quickly looking at my scripts.

-and

You can combine expressions with the -and operator. When you do that, both sides need to be $true for the whole expression to be $true.

if ( ($age -gt 13) -and ($age -lt 55) )

In that example, $age must be 13 or older for the left side and less than 55 for the right side. I added extra parentheses to make it clearer in that example but they're optional as long as the expression is simple. Here is the same example without them.

if ( $age -gt 13 -and $age -lt 55 )

Evaluation happens from left to right. If the first item evaluates to $false, it exits early and doesn't perform the right comparison. This is handy when you need to make sure a value exists before you use it. For example, Test-Path throws an error if you give it a $null path.

if ( $null -ne $path -and (Test-Path -Path $path) )

-or

The -or allows for you to specify two expressions and returns $true if either one of them is $true.

if ( $age -le 13 -or $age -ge 55 )

Just like with the -and operator, the evaluation happens from left to right. Except that if the first part is $true, then the whole statement is $true and it doesn't process the rest of the expression.

Also make note of how the syntax works for these operators. You need two separate expressions. I have seen users try to do something like this $value -eq 5 -or 6 without realizing their mistake.

-xor exclusive or

This one is a little unusual. -xor allows only one expression to evaluate to $true. So if both items are $false or both items are $true, then the whole expression is $false. Another way to look at this is the expression is only $true when the results of the expression are different.

It's rare that anyone would ever use this logical operator and I can't think up a good example as to why I would ever use it.

Bitwise operators

Bitwise operators perform calculations on the bits within the values and produce a new value as the result. Teaching bitwise operators is beyond the scope of this article, but here is the list of them.

  • -band binary AND
  • -bor binary OR
  • -bxor binary exclusive OR
  • -bnot binary NOT
  • -shl shift left
  • -shr shift right

PowerShell expressions

We can use normal PowerShell inside the condition statement.

if ( Test-Path -Path $Path )

Test-Path returns $true or $false when it executes. This also applies to commands that return other values.

if ( Get-Process Notepad* )

It evaluates to $true if there's a returned process and $false if there isn't. It's perfectly valid to use pipeline expressions or other PowerShell statements like this:

if ( Get-Process | Where Name -eq Notepad )

These expressions can be combined with each other with the -and and -or operators, but you may have to use parenthesis to break them into subexpressions.

if ( (Get-Process) -and (Get-Service) )

Checking for $null

Having a no result or a $null value evaluates to $false in the if statement. When checking specifically for $null, it's a best practice to place the $null on the left-hand side.

if ( $null -eq $value )

There are quite a few nuances when dealing with $null values in PowerShell. If you're interested in diving deeper, I have an article about everything you wanted to know about $null.

Variable assignment within the condition

I almost forgot to add this one until Prasoon Karunan V reminded me of it.

if ($process=Get-Process notepad -ErrorAction ignore) {$process} else {$false}

Normally when you assign a value to a variable, the value isn't passed onto the pipeline or console. When you do a variable assignment in a sub expression, it does get passed on to the pipeline.

PS> $first = 1
PS> ($second = 2)
2

See how the $first assignment has no output and the $second assignment does? When an assignment is done in an if statement, it executes just like the $second assignment above. Here is a clean example on how you could use it:

if ( $process = Get-Process Notepad* )
{
    $process | Stop-Process
}

If $process gets assigned a value, then the statement is $true and $process gets stopped.

Make sure you don't confuse this with -eq because this isn't an equality check. This is a more obscure feature that most people don't realize works this way.

Variable assignment from the scriptblock

You can also use the if statement scriptblock to assign a value to a variable.

$discount = if ( $age -ge 55 )
{
    Get-SeniorDiscount
}
elseif ( $age -le 13 )
{
    Get-ChildDiscount
}
else
{
    0.00
}

Each script block is writing the results of the commands, or the value, as output. We can assign the result of the if statement to the $discount variable. That example could have just as easily assigned those values to the $discount variable directly in each scriptblock. I can't say that I use this with the if statement often, but I do have an example where I used this recently.

Alternate execution path

The if statement allows you to specify an action for not only when the statement is $true, but also for when it's $false. This is where the else statement comes into play.

else

The else statement is always the last part of the if statement when used.

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
else
{
    Write-Warning "$path doesn't exist or isn't a file."
}

In this example, we check the $path to make sure it's a file. If we find the file, we move it. If not, we write a warning. This type of branching logic is very common.

Nested if

The if and else statements take a script block, so we can place any PowerShell command inside them, including another if statement. This allows you to make use of much more complicated logic.

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
else
{
    if ( Test-Path -Path $Path )
    {
        Write-Warning "A file was required but a directory was found instead."
    }
    else
    {
        Write-Warning "$path could not be found."
    }
}

In this example, we test the happy path first and then take action on it. If that fails, we do another check and to provide more detailed information to the user.

elseif

We aren't limited to just a single conditional check. We can chain if and else statements together instead of nesting them by using the elseif statement.

if ( Test-Path -Path $Path -PathType Leaf )
{
    Move-Item -Path $Path -Destination $archivePath
}
elseif ( Test-Path -Path $Path )
{
    Write-Warning "A file was required but a directory was found instead."
}
else
{
    Write-Warning "$path could not be found."
}

The execution happens from the top to the bottom. The top if statement is evaluated first. If that is $false, then it moves down to the next elseif or else in the list. That last else is the default action to take if none of the others return $true.

switch

At this point, I need to mention the switch statement. It provides an alternate syntax for doing multiple comparisons with a value. With the switch, you specify an expression and that result gets compared with several different values. If one of those values match, the matching code block is executed. Take a look at this example:

$itemType = 'Role'
switch ( $itemType )
{
    'Component'
    {
        'is a component'
    }
    'Role'
    {
        'is a role'
    }
    'Location'
    {
        'is a location'
    }
}

There three possible values that can match the $itemType. In this case, it matches with Role. I used a simple example just to give you some exposure to the switch operator. I talk more about everything you ever wanted to know about the switch statement in another article.

Array inline

I have a function called Invoke-SnowSql that launches an executable with several command-line arguments. Here is a clip from that function where I build the array of arguments.

$snowSqlParam = @(
    '--accountname', $Endpoint
    '--username', $Credential.UserName
    '--option', 'exit_on_error=true'
    '--option', 'output_format=csv'
    '--option', 'friendly=false'
    '--option', 'timing=false'
    if ($Debug)
    {
        '--option', 'log_level=DEBUG'
    }
    if ($Path)
    {
        '--filename', $Path
    }
    else
    {
        '--query', $singleLineQuery
    }
)

The $Debug and $Path variables are parameters on the function that are provided by the end user. I evaluate them inline inside the initialization of my array. If $Debug is true, then those values fall into the $snowSqlParam in the correct place. Same holds true for the $Path variable.

Simplify complex operations

It's inevitable that you run into a situation that has way too many comparisons to check and your If statement scrolls way off the right side of the screen.

$user = Get-ADUser -Identity $UserName
if ( $null -ne $user -and $user.Department -eq 'Finance' -and $user.Title -match 'Senior' -and $user.HomeDrive -notlike '\\server\*' )
{
    # Do Something
}

They can be hard to read and that make you more prone to make mistakes. There are a few things we can do about that.

Line continuation

There some operators in PowerShell that let you wrap you command to the next line. The logical operators -and and -or are good operators to use if you want to break your expression into multiple lines.

if ($null -ne $user -and
    $user.Department -eq 'Finance' -and
    $user.Title -match 'Senior' -and
    $user.HomeDrive -notlike '\\server\*'
)
{
    # Do Something
}

There's still a lot going on there, but placing each piece on its own line makes a big difference. I generally use this when I get more than two comparisons or if I have to scroll to the right to read any of the logic.

Pre-calculating results

We can take that statement out of the if statement and only check the result.

$needsSecureHomeDrive = $null -ne $user -and
    $user.Department -eq 'Finance' -and
    $user.Title -match 'Senior' -and
    $user.HomeDrive -notlike '\\server\*'

if ( $needsSecureHomeDrive )
{
    # Do Something
}

This just feels much cleaner than the previous example. You also are given an opportunity to use a variable name that explains what it's that you're really checking. This is also and example of self-documenting code that saves unnecessary comments.

Multiple if statements

We can break this up into multiple statements and check them one at a time. In this case, we use a flag or a tracking variable to combine the results.


$skipUser = $false

if( $null -eq $user )
{
    $skipUser = $true
}

if( $user.Department -ne 'Finance' )
{
    Write-Verbose "isn't in Finance department"
    $skipUser = $true
}

if( $user.Title -match 'Senior' )
{
    Write-Verbose "Doesn't have Senior title"
    $skipUser = $true
}

if( $user.HomeDrive -like '\\server\*' )
{
    Write-Verbose "Home drive already configured"
    $skipUser = $true
}

if ( -not $skipUser )
{
    # do something
}

I did have to invert the logic to make the flag logic work correctly. Each evaluation is an individual if statement. The advantage of this is that when you're debugging, you can tell exactly what the logic is doing. I was able to add much better verbosity at the same time.

The obvious downside is that it's so much more code to write. The code is more complex to look at as it takes a single line of logic and explodes it into 25 or more lines.

Using functions

We can also move all that validation logic into a function. Look at how clean this looks at a glance.

if ( Test-SecureDriveConfiguration -ADUser $user )
{
    # do something
}

You still have to create the function to do the validation, but it makes this code much easier to work with. It makes this code easier to test. In your tests, you can mock the call to Test-ADDriveConfiguration and you only need two tests for this function. One where it returns $true and one where it returns $false. Testing the other function is simpler because it's so small.

The body of that function could still be that one-liner we started with or the exploded logic that we used in the last section. This works well for both scenarios and allows you to easily change that implementation later.

Error handling

One important use of the if statement is to check for error conditions before you run into errors. A good example is to check if a folder already exists before you try to create it.

if ( -not (Test-Path -Path $folder) )
{
    New-Item -Type Directory -Path $folder
}

I like to say that if you expect an exception to happen, then it's not really an exception. So check your values and validate your conditions where you can.

If you want to dive a little more into actual exception handling, I have an article on everything you ever wanted to know about exceptions.

Final words

The if statement is such a simple statement but is a fundamental piece of PowerShell. You will find yourself using this multiple times in almost every script you write. I hope you have a better understanding than you had before.