Manage Learn to apply best practices and optimize your operations.

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

Even though it's a common problem that can frustrate administrators, it's possible to compare PowerShell variables by learning some simple commands.

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

$file = Dir C:\MyDirectory
if ($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. 

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.

Dig Deeper on Windows administration tools

Join the conversation

1 comment

Send me notifications when other members comment.

Please create a username to comment.

That's the great weakness of Powershell and any other program all the way down to SQL: It is written by logic freaks who are unwilling to give an inch, so that a person writing a "plain english" command, such as "Get me all files with "reason" in the file name and the words "smart programming" in the body, and sort them by date, and thereafter alphabethically., could indeed communicate with a "logic freak derived thing called powershell". When indeed "powershell" should be called "weakshell" and "SQL" should be named "UCatUQL" i.e. "Unnecessarily complicated and thus useless Query language. How long do we have to wait before the software industry wakes up to these problems. Forever!