PowerShell v3 tip

Why can't I compare the contents of PowerShell variables?

Here's a brief code snippet that illustrates one of the most common PowerShell issues:

$file = Dir C:\MyDirectory
if

Requires Free Membership to View

($file.name –like '*archive*') {  
Write-Host "$($file.name) is an archive"
}

The intent is to look at a lot of files and determine whether they have the word "archive" in their filename. Unfortunately, this script makes some classic mistakes.

First, the Dir command in PowerShell has the potential to return multiple objects as well as multiple types of objects. When the command runs in a file system drive, it can return files and folders. In this case, the script would be okay with the command, since both files and folders have a Name property ($file.name references the name property), but this might not be the behavior you want. In PowerShell v3, you can limit the Dir command to returning just files by adding the –File parameter. Read the help for the command to see other limitations you can specify.

Second, the $file variable in this script will almost always contain multiple objects, because most folders on the file system contain other folders and multiple files. This is not always a problem because PowerShell is fine putting a lot of items into one variable.

The problem comes with the comparison:

if ($file.name –like '*archive*') {

Which file's Name property are you trying to compare to "*archive*"? In PowerShell v3, it's completely legal to use something like $file.name. Under the hood, PowerShell interprets the command as, "I'd like to access the Name property of every object in the $file variable, please." The result of that is still multiple objects. In this case, the result includes multiple String objects that contain file and folder names.

The problem is that PowerShell's comparison operators, such as the –like operator, can't deal with multiple objects. These operators are designed to return True and False values. What are the operators supposed to do if some file names contain "archive" and others don't, return a "Maybe" value?

The way to solve this problem is to enumerate the files so you're only comparing one at a time.

For PowerShell v3, the correct way to do this is:

$files = Dir C:\MyDirectory –File
foreach ($file in $files) {  
if ($file.name –like '*archive*') {    
Write-Host "$($file.name) is an archive"  
}
}

The Foreach construct is designed to accept multiple objects in its second argument ($files) then move through that collection one object at a time. Each time through, the construct takes one object from the collection and puts it into the variable the first argument specifies ($file, in this example). You can work with just one object at a time this way.

While it's common to use singular and plural versions of the same word -- $file and $files, for example -- you don't need to with Foreach. Doing so makes your code more human-readable, but PowerShell doesn't care.

The following is equally valid:

$apples = Dir C:\MyDirectory –File
foreach ($fred in $apples) {
  if ($fred.name –like '*archive*') {
    Write-Host "$($fred.name) is an archive"
  }
}

In PowerShell, there are multiple ways to do almost anything. Here's another variant of the same technique:

Dir C:\MyDirectory –File |
foreach-object {
if ($_.name –like '*archive*') {
Write-Host "$($_.name) is an archive"  
}
}

This technique uses the ForEach-Object cmdlet instead of the Foreach scripting construction. The cmdlet uses a different syntax and you don't get to specify variable names in it. Instead, it gives you a predetermined variable ($) that contains just one object at a time from the collection of objects piped into the cmdlet. Both techniques run in about the same amount of time and both are completely valid.

You could also do something like this:

Dir C:\MyDirectory –File |
where-object { $_.name –like '*archive*' } |
foreach-object {  
Write-Host "$($_.name) is an archive"
}

This uses a command-line type of technique instead of a scripting-style technique, but it's completely valid.

One of the toughest parts about learning PowerShell is the number of valid approaches you can adopt for any given task. It seems like you have to know them all if you're going to successfully repurpose other examples. But this variety also means you can adopt approaches most familiar to you and you can leverage any existing skills you bring to the situation.

Folks who know me probably wonder why I haven't ranted about the use of Write-Host in this example. It's definitely not an optimal way to produce output from a script...but that's a topic for a future tip. 

ABOUT THE AUTHOR
Don Jones is one of the world's best-known and respected PowerShell experts and educators. He's the co-author of three books on PowerShell (see http://PowerShellBooks.com for a list), and you can find him online, including his PowerShell Q&A forums, by visiting http://DonJones.com.

This was first published in August 2012

There are Comments. Add yours.

 
TIP: Want to include a code block in your comment? Use <pre> or <code> tags around the desired text. Ex: <code>insert code</code>

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy
Sort by: OldestNewest

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to: