Chapter 7. Conditions

by Mar 15, 2012

Conditions are what you need to make scripts clever. Conditions can evaluate a situation and then take appropriate action. There are a number of condition constructs in the PowerShell language which that we will look at in this chapter.

In the second part, you'll employ conditions to execute PowerShell instructions only if a particular condition is actually met.

Topics Covered:

Creating Conditions

A condition is really just a question that can be answered with yes (true) or no (false). The following PowerShell comparison operators allow you to compare values,

Operator Conventional Description Example Result
-eq, -ceq, -ieq = equals 10 -eq 15 $false
-ne, -cne, -ine <> not equal 10 -ne 15 $true
-gt, -cgt, -igt > greater than 10 -gt 15 $false
-ge, -cge, -ige >= greater than or equal to 10 -ge 15 $false
-lt, -clt, -ilt < less than 10 -lt 15 $true
-le, -cle, -ile <= less than or equal to 10 -le 15 $true
-contains,
-ccontains,
-icontains
contains 1,2,3 -contains 1 $true
-notcontains,
-cnotcontains,
-inotcontains
does not contain 1,2,3 -notcontains 1 $false

Table 7.1: Comparison operators

PowerShell doesn't use traditional comparison operators that you may know from other programming languages. In particular, the "=" operator is an assignment operator only in PowerShell, while ">" and "<" operators are used for redirection.

There are three variants of all comparison operators. The basic variant is case-insensitive so it does not distinguish between upper and lower case letters (if you compare text). To explicitly specify whether case should be taken into account, you can use variants that begin with "c" (case-sensitive) or "i" (case-insensitive).

Carrying Out a Comparison

To get familiar with comparison operators, you can play with them in the interactive PowerShell console! First, enter a value, then a comparison operator, and then the second value that you want to compare with the first. When you hit (enter)), PowerShell executes the comparison. The result is always True (condition is met) or False (condition not met).

4 -eq 10 
False
"secret" -ieq "SECRET"  
True

As long as you compare only numbers or only strings, comparisons are straight-forward:

123 -lt 123.5 
True

However, you can also compare different data types. However, these results are not always as straight-forward as the previous one:

12 -eq "Hello" 
False
12 -eq "000012" 
True
"12" -eq 12 
True
"12" -eq 012 
True
"012" -eq 012 
False
123 lt 123.4 
True
123 lt "123.4" 
False
123 lt "123.5" 
True

Are the results surprising? When you compare different data types, PowerShell will try to convert the data types into one common data type. It will always look at the data type to the left of the comparison operator and then try and convert the value to the right to this data type.

"Reversing" Comparisons

With the logical operator -not you can reverse comparison results. It will expect an expression on the right side that is either true or false. Instead of -not, you can also use "!":

$a = 10 
$a -gt 5 
True
-not ($a -gt 5) 
False

# Shorthand: instead of -not "!" can also be used:
!($a -gt 5)
False

You should make good use of parentheses if you're working with logical operators like –not. Logical operators are always interested in the result of a comparison, but not in the comparison itself. That's why the comparison should always be in parentheses.

Combining Comparisons

You can combine several comparisons with logical operators because every comparison returns either True or False. The following conditional statement would evaluate to true only if both comparisons evaluate to true:

( ($age -ge 18) -and ($sex -eq "m") )

You should put separate comparisons in parentheses because you only want to link the results of these comparisons and certainly not the comparisons themselves.

Operator Description Left Value Right Value Result
-and Both conditions must be met True
False
False
True
False
True
False
True
False
False
False
True
-or At least one of the two conditions must be met True
False
False
True
False
True
False
True
True
True
False
True
-xor One or the other condition must be met, but not both True
False
False
True
True
False
True
False
False
False
True
True
-not Reverses the result (not applicable) True
False
False
True

Table 7.2: Logical operators

Comparisons with Arrays and Collections

Up to now, you've only used the comparison operators in Table 7.1 to compare single values. In Chapter 4, you’ve already become familiar with arrays. How do comparison operators work on arrays? Which element of an array is used in the comparison? The simple answer is all elements!

In this case, comparison operators work pretty much as a filter and return a new array that only contains the elements that matched the comparison.

1,2,3,4,3,2,1 -eq 3 
3
3

If you'd like to see only the elements of an array that don't match the comparison value, you can use -ne (not equal) operator:

1,2,3,4,3,2,1 -ne 3 
1
2
4
2
1

Verifying Whether an Array Contains a Particular Element

But how would you find out whether an array contains a particular element? As you have seen, -eq provides matching array elements only. -contains and -notcontains. verify whether a certain value exists in an array.

# -eq returns only those elements matching the criterion:
1,2,3 eq 5 

# -contains answers the question of whether the sought element is included in the array:
1,2,3 -contains 5 
False
1,2,3 -notcontains 5 
True

Where-Object

In the pipeline, the results of a command are handed over to the next one and the Where-Object cmdlet will work like a filter, allowing only those objects to pass the pipeline that meet a certain condition. To make this work, you can specify your condition to Where-Object.

Filtering Results in the Pipeline

The cmdlet Get-Process returns all running processes. If you would like to find out currently running instances of Notepad, you will need to set up the appropriate comparison term. You will first need to know the names of all the properties found in process objects. Here is one way of listing them:

Get-Process | Select-Object -first 1 *
__NounName                 : process
Name                       : agrsmsvc
Handles                    : 36
VM                         : 21884928
WS                         : 57344
PM                         : 716800
NPM                        : 1768
Path                       :
Company                    :
CPU                        :
FileVersion                :
ProductVersion             :
Description                :
Product                    :
Id                         : 1316
PriorityClass              :
HandleCount                : 36
WorkingSet                 : 57344
PagedMemorySize            : 716800
PrivateMemorySize          : 716800
VirtualMemorySize          : 21884928
TotalProcessorTime         :
BasePriority               : 8
ExitCode                   :
HasExited                  :
ExitTime                   :
Handle                     :
MachineName                : .
MainWindowHandle           : 0
MainWindowTitle            :
MainModule                 :
MaxWorkingSet              :
MinWorkingSet              :
Modules                    :
NonpagedSystemMemorySize   : 1768
NonpagedSystemMemorySize64 : 1768
PagedMemorySize64          : 716800
PagedSystemMemorySize      : 24860
PagedSystemMemorySize64    : 24860
PeakPagedMemorySize        : 716800
PeakPagedMemorySize64      : 716800
PeakWorkingSet             : 2387968
PeakWorkingSet64           : 2387968
PeakVirtualMemorySize      : 21884928
PeakVirtualMemorySize64    : 21884928
PriorityBoostEnabled       :
PrivateMemorySize64        : 716800
PrivilegedProcessorTime    :
ProcessName                : agrsmsvc
ProcessorAffinity          :
Responding                 : True
SessionId                  : 0
StartInfo                  : System.Diagnostics.ProcessStartInfo
StartTime                  :
SynchronizingObject        :
Threads                    : {1964, 1000}
UserProcessorTime          :
VirtualMemorySize64        : 21884928
EnableRaisingEvents        : False
StandardInput              :
StandardOutput             :
StandardError              :
WorkingSet64               : 57344
Site                       :
Container                  :

Putting Together a Condition

As you can see from the previous output, the name of a process can be found in the Name property. If you're just looking for the processes of the Notepad, your condition is: name -eq 'notepad:

Get-Process | Where-Object { $_.name -eq 'notepad' }
Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     68       4     1636       8744    62     0,14   7732 notepad
     68       4     1632       8764    62     0,05   7812 notepad

Here are two things to note: if the call does not return anything at all, then there are probably no Notepad processes running. Before you make the effort and use Where-Object to filter results, you should make sure the initial cmdlet has no parameter to filter the information you want right away. For example, Get-Process already supports a parameter called -name, which will return only the processes you specify:

Get-Process -name notepad
Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     68       4     1636       8744    62     0,14   7732 notepad
     68       4     1632       8764    62     0,05   7812 notepad

The only difference with the latter approach: if no Notepad process is running, Get-Process throws an exception, telling you that there is no such process. If you don't like that, you can always add the parameter -ErrorAction SilentlyContinue, which will work for all cmdlets and hide all error messages.

When you revisit your Where-Object line, you'll see that your condition is specified in curly brackets after the cmdlet. The $_ variable contains the current pipeline object. While sometimes the initial cmdlet is able to do the filtering all by itself (like in the previous example using -name), Where-Object is much more flexible because it can filter on any piece of information found in an object.

You can use the next one-liner to retrieve all processes whose company name begins with "Micro" and output name, description, and company name:

Get-Process | Where-Object { $_.company -like 'micro*' } | Select-Object name, description, company
Name                               Description                       Company
----                               -----------                       -------
conime                             Console IME                       Microsoft Corporation
dwm                                Desktopwindow-Manager             Microsoft Corporation
ehmsas                             Media Center Media Status Aggr... Microsoft Corporation
ehtray                             Media Center Tray Applet          Microsoft Corporation
EXCEL                              Microsoft Office Excel            Microsoft Corporation
explorer                           Windows-Explorer                  Microsoft Corporation
GrooveMonitor                      GrooveMonitor Utility             Microsoft Corporation
ieuser                             Internet Explorer                 Microsoft Corporation
iexplore                           Internet Explorer                 Microsoft Corporation
msnmsgr                            Messenger                         Microsoft Corporation
notepad                            Editor                            Microsoft Corporation
notepad                            Editor                            Microsoft Corporation
sidebar                            Windows-Sidebar                   Microsoft Corporation
taskeng                            Task Scheduler Engine             Microsoft Corporation
WINWORD                            Microsoft Office Word             Microsoft Corporation
wmpnscfg                           Windows Media Player Network S... Microsoft Corporation
wpcumi                             Windows Parental Control Notif... Microsoft Corporation

Since you will often need conditions in a pipeline, there is an alias for Where-Object: "?". So, instead of Where-Object, you can also use "?'". However, it does make your code a bit unreadable:

# The two following instructions return the same result: all running services
Get-Service | Foreach-Object {$_.Status -eq 'Running' }
Get-Service | ? {$_.Status -eq 'Running' }

If-ElseIf-Else

Where-object works great in the pipeline, but it is inappropriate if you want to make longer code segments dependent on meeting a condition. Here, the If..ElseIf..Else statement works much better. In the simplest case, the statement will look like this:

If (condition) {# If the condition applies, this code will be executed}

The condition must be enclosed in parentheses and follow the keyword If. If the condition is met, the code in the curly brackets after it will be executed, otherwise, it will not. Try it out:

If ($a -gt 10) { "$a is larger than 10" } 

It's likely, though, that you won't (yet) see a result. The condition was not met, and so the code in the curly brackets wasn't executed. To get an answer, you can make sure that the condition is met:

$a = 11 
if ($a -gt 10) { "$a is larger than 10" } 
11 is larger than 10

Now, the comparison is true, and the If statement ensures that the code in the curly brackets will return a result. As it is, that clearly shows that the simplest If statement usually doesn't suffice in itself, because you would like to always get a result, even when the condition isn't met. You can expand the If statement with Else to accomplish that:

if ($a -gt 10) 
{
  "$a is larger than 10"
} 
else
{
  "$a is less than or equal to 10"
}

Now, the code in the curly brackets after If is executed if the condition is met. However, if the preceding condition isn’t true, the code in the curly brackets after Else will be executed. If you have several conditions, you may insert as many ElseIf blocks between If and Else as you like:

if ($a -gt 10) 
{
  "$a is larger than 10"
} 
elseif ($a -eq 10) 
{
  "$a is exactly 10"
} 
else 
{
  "$a is less than 10"
}

The If statement here will always execute the code in the curly brackets after the condition that is met. The code after Else will be executed when none of the preceding conditions are true. What happens if several conditions are true? Then the code after the first applicable condition will be executed and all other applicable conditions will be ignored.

if ($a -gt 10) 
{
  "$a is larger than 10"
} 
elseif ($a -eq 10) 
{
  "$a is exactly 10"
} 
elseif ($a ge 10) 
{
  "$a is larger than or equal to 10"
} 
else 
{
  "$a is smaller than 10"
}

The fact is that the If statement doesn't care at all about the condition that you state. All that the If statement evaluates is $true or $false. If condition evaluates $true, the code in the curly brackets after it will be executed, otherwise, it will not. Conditions are only a way to return one of the requested values $true or $false. But the value could come from another function or from a variable:

# Returns True from 14:00 on, otherwise False:
function isAfternoon { (get-date).Hour -gt 13 }
isAfternoon
True

# Result of the function determines which code the If statement executes:
if (isAfternoon) { "Time for break!" } else { "It’s still early." }
Time for break!

This example shows that the condition after If must always be in parentheses, but it can also come from any source as long as it is $true or $false. In addition, you can also write the If statement in a single line. If you'd like to execute more than one command in the curly brackets without having to use new lines, then you should separate the commands with a semi-colon ";".

Switch

If you'd like to test a value against many comparison values, the If statement can quickly become unreadable. The Switch code is much cleaner:

# Test a value against several comparison values (with If statement):
$value = 1
if ($value -eq 1)
{ 
  " Number 1"
}
elseif ($value -eq 2)
{ 
  " Number 2"
}
elseif ($value -eq 3)
{ 
  " Number 3"
}
Number 1

# Test a value against several comparison values (with Switch statement):
$value = 1
switch ($value)
{
  1  { "Number 1" }
  2  { "Number 2" }
  3  { "Number 3" }
}
Number 1

This is how you can use the Switch statement: the value to switch on is in the parentheses after the Switch keyword. That value is matched with each of the conditions on a case-by-case basis. If a match is found, the action associated with that condition is then performed. You can use the default comparison operator, the –eq operator, to verify equality.

Testing Range of Values

The default comparison operator in a switch statement is -eq, but you can also compare a value with other comparison statements. You can create your own condition and put it in curly brackets. The condition must then result in either true or false:

$value = 8
switch ($value)
{
  # Instead of a standard value, a code block is used that results in True for numbers smaller than 5:
  {$_ -le 5}  { "Number from 1to 5" }

  # A value is used here; Switch checks whether this value matches $value:
  6  { "Number 6" }

  # Complex conditions areallowed as they are here, where –and is used to combine two comparisons:
  {(($_ -gt 6) -and ($_ -le 10))}  { "Number from 7 to 10" }
}
Number from 7 to 10

  • The code block {$_ -le 5} includes all numbers less than or equal to 5.
  • The code block {(($_ -gt 6) -and ($_ -le 10))} combines two conditions and results in true if the number is either larger than 6 or less than-equal to 10. Consequently, you can combine any PowerShell statements in the code block and also use the logical operators listed in Table 7.2.

Here, you can use the initial value stored in $_ for your conditions, but because $_ is generally available anywhere in the Switch block, you could just as well have put it to work in the result code:

$value = 8
switch ($value)
{
  # The initial value (here it is in $value) is available in the variable $_:
  {$_ -le 5}  { "$_ is a number from 1 to 5" }
  6  { "Number 6" }
  {(($_ -gt 6) -and ($_ -le 10))}  { "$_ is a number from 7 to 10" }
}
8 is a number from 7 to 10

No Applicable Condition

In contrast to If, the Switch clause will execute all code for all conditions that are met. So, if there are two conditions that are both met, Switch will execute them both whereas If had only executed the first matching condition code. To change the Switch default behavior and make it execute only the first matching code, you should use the statement continue inside of a code block.

If no condition is met, the If clause will provide the Else statement, which serves as a catch-all. Likewise, Switch has a similar catch-all called default:

$value = 50
switch ($value)
{
  {$_ -le 5}  { "$_is a number from 1 to 5" }
  6  { "Number 6" }
  {(($_ -gt 6) -and ($_ -le 10))}  { "$_ is a number from 7 to 10" }
  # The code after the next statement will be executed if no other condition has been met:
  default {"$_ is a number outside the range from 1 to 10" }
}
50 is a number outside the range from 1 to 10

Several Applicable Conditions

If more than one condition applies, then Switch will work differently from If. For If, only the first applicable condition was executed. For Switch, all applicable conditions are executed:

$value = 50
switch ($value)
{
  50  { "the number 50" }
  {$_ -gt 10}  {"larger than 10"}
  {$_ -is [int]}  {"Integer number"}
}
The Number 50
Larger than 10
Integer number

Consequently, all applicable conditions will ensure that the following code is executed. So in some circumstances, you may get more than one result.

Try out that example, but assign 50.0 to $value. In this case, you'll get just two results instead of three. Do you know why? That's right: the third condition is no longer fulfilled because the number in $value is no longer an integer number. However, the other two conditions continue to remain fulfilled.

If you'd like to receive only one result, you can add the continue or break statement to the code.

$value = 50
switch ($value)
{
  50  { "the number 50" break }
  {$_ -gt 10}  {"larger than 10" break}
  {$_ -is [int]}  {"Integer number" break}
}
The number 50

The keyword break tells PowerShell to leave the Switch construct. In conditions, break and continue are interchangeable. In loops, they work differently. While breaks exits a loop immediately, continue would only exit the current iteration.

Using String Comparisons

The previous examples have compared numbers. You could also naturally compare strings since you now know that Switch uses only the normal –eq comparison operator behind the scenes and that their string comparisons are also permitted.. The following code could be the basic structure of a command evaluation. As such, a different action will be performed, depending on the specified command:

$action = "sAVe"
switch ($action)
{
  "save"  { "I save..." }
  "open"  { "I open..." }
  "print"  { "I print..." }
  Default  { "Unknown command" }
}
I save...

Case Sensitivity

Since the –eq comparison operator doesn't distinguish between lower and upper case, case sensitivity doesn't play a role in comparisons. If you want to distinguish between them, you can use the –case option. Working behind the scenes, it will replace the –eq comparison operator with –ceq, after which case sensitivity will suddenly become crucial:

$action = "sAVe"
switch -case ($action)
{
  "save"  { "I save..." }
  "open"  { "I open..." }
  "print"  { "I print..." }
  Default  { "Unknown command" }
}
Unknown command

Wildcard Characters

In fact, you can also exchange a standard comparison operator for –like and –match operators and then carry out wildcard comparisons. Using the –wildcard option, you can activate the -like operator, which is conversant, among others, with the "*" wildcard character:

$text = "IP address: 10.10.10.10"
switch -wildcard ($text)
{
  "IP*"  { "The text begins with IP: $_" }
  "*.*.*.*"  { "The text contains an IP address string pattern: $_" }
  "*dress*"  { "The text contains the string 'dress' in arbitrary locations: $_" }
}
The text begins with IP: IP address: 10.10.10.10 
The text contains an IP address string pattern: IP address: 10.10.10.10
The text contains the string 'dress' in arbitrary locations: IP address: 10.10.10.10

Regular Expressions

Simple wildcard characters ca not always be used for recognizing patterns. Regular expressions are much more efficient. But they assume much more basic knowledge, which is why you should take a peek ahead at Chapter 13, discussion of regular expression in greater detail.

With the -regex option, you can ensure that Switch uses the –match comparison operator instead of –eq, and thus employs regular expressions. Using regular expressions, you can identify a pattern much more precisely than by using simple wildcard characters. But that's not all!. As in the case with the –match operator, you will usually get back the text that matches the pattern in the $matches variable. This way, you can even parse information out of the text:

$text = "IP address: 10.10.10.10"
switch -regex ($text)
{
  "^IP"  { "The text begins with IP: $($matches[0])" }
  "d{1,3}.d{1,3}.d{1,3}.d{1,3}"  { "The text contains an IP address string pattern: $($matches[0])" }
  "b.*?dress.*?b"  { " The text contains the string 'dress' in arbitrary locations: $($matches[0])" }
}
The text begins with IP: IP
The text contains an IP address string pattern: 10.10.10.10
The text contains the string 'dress' in arbitrary locations: IP address

The result of the –match comparison with the regular expression is returned in $matches, a hash table with each result, because regular expressions can, depending on their form, return several results. In this example, only the first result you got by using $matches[0] should interest you.. The entire expression is embedded in $(…) to ensure that this result appears in the output text.

Processing Several Values Simultaneously

Until now, you have always passed just one value for evaluation to Switch. But Switch can also process several values at the same time. To do so, you can pass to Switch the values in an array or a collection. In the following example, Switch is passed an array containing five elements. Switch will automatically take all the elements, one at a time, from the array and compare each of them, one by one:

$array = 1..5
switch ($array)
{
  {$_ % 2} { "$_ is uneven."}
  Default { "$_ is even."}
}
1 is uneven.
2 is even.
3 is uneven.
4 is even.
5 is uneven.

There you have it: Switch will accept not only single values, but also entire arrays and collections. As such, Switch would be an ideal candidate for evaluating results on the PowerShell pipeline because the pipeline character ("|") is used to forward results as arrays or collections from one command to the next.

The next line queries Get-Process for all running processes and then pipes the result to a script block (& {…}). In the script block, Switch will evaluate the result of the pipeline, which is available in $input. If the WS property of a process is larger than one megabyte, this process is output. Switch will then filter all of the processes whose WS property is less than or equal to one megabyte:

Get-Process | & { Switch($input) { {$_.WS -gt 1MB} { $_ }}}

However, this line is extremely hard to read and seems complicated. You can formulate the condition in a much clearer way by using Where-Object:

Get-Process | Where-Object { $_.WS -gt 1MB }

This variant also works more quickly because Switch had to wait until the pipeline has collected the entire results of the preceding command in $input. In Where-Object, it processes the results of the preceding command precisely when the results are ready. This difference is especially striking for elaborate commands:

# Switch returns all files beginning with "a":
Dir | & { switch($Input) { {$_.name.StartsWith("a")} { $_ } }}

# But it doesn't do so until Dir has retrieved all data, and that can take a long time:
Dir -Recurse | & { switch($Input) { {$_.name.StartsWith("a")} { $_ } }}

# Where-Object processes the incoming results immediately:
Dir -recurse | Where-Object { $_.name.StartsWith("a") }

# The alias of Where-Object ("?") works exactly the same way:
Dir -recurse | ? { $_.name.StartsWith("a") }

Summary

Intelligent decisions are based on conditions, which in their simplest form can be reduced to plain Yes or No answers. Using the comparison operators listed in Table 7.1, you can formulate such conditions and even combine these with the logical operators listed in Table 7.2 to form complex queries.

The simple Yes/No answers of your conditions will determine whether particular PowerShell instructions can carried out or not. In their simplest form, you can use the Where-Object cmdlet in the pipeline. It functions there like a filter, allowing only those results through the pipeline that correspond to your condition.

If you would like more control, or would like to execute larger code segments independently of conditions, you can use the If statement, which evaluates as many different conditions as you wish and, depending on the result, will then execute the allocated code. This is the typical "If-Then" scenario: if certain conditions are met, then certain code segments will be executed.

An alternative to the If statement is the Switch statement. Using it, you can compare a fixed initial value with various possibilities. Switch is the right choice when you want to check a particular variable against many different possible values.