Simplify Your PowerShell Code by Using a Compound Where Clause

Simplify Your PowerShell Code by Using a Compound Where Clause

  • Comments 7
  • Likes

Summary: Microsoft Scripting Guy, Ed Wilson, talks about a couple of issues he saw while grading scripts for the 2012 Windows PowerShell Scripting Games.

Microsoft Scripting Guy, Ed Wilson, is here. The Scripting Wife and I are in Atlanta for SQL Saturday #111. I will be talking about Windows PowerShell and SQL best practices. One of the great things about the Scripting Games is seeing what other people are doing. When you are the Microsoft Scripting Guy, it is good to see where people still have some confusion, and then use that information to generate ideas for blog posts. I am not alone in this endeavor. I saw a tweet the other day that said the person was generating a huge list of subjects for blog posts as a result of the 2012 Scripting Games. Today I want to look at a few things that caused me to repeat myself over and over again as I was grading scripts.

The mystery of the compound Where clause

In Windows PowerShell 3.0, we have simplified the simple Where clause. I am not going to write about that right now, but it is enough to say that when you have a single condition, it is easier to write in Windows PowerShell 3.0 than in Windows PowerShell 2.0. But there are many times when you want to use a compound Where clause. For example, Beginner Event 3 requires you to find running services that also support a Stop command. I saw lots of scripts where the person obviously did not know about using a compound Where clause. Here is one such example:

gsv -computername localhost | where {($_.status -eq "Running") -and ($_.CanStop -eq $true)}

Now, there is nothing wrong with this line of code—in fact, it meets the needs of the scenario, and it works perfectly fine. But the code can be simplified by using a compound Where clause. Simple and compound do not often go together, but they do here. Here is the way I would write the Where clause:

where { $_.status -eq 'running' -and $_.canstop }

Notice, that instead of having two separate groupings, I in fact, have no grouping at all. (The grouping is the parentheses.) The compound Where clause says where the status of the current object (the $_ represents, the current object in the pipeline) is equal to Running. It also says, AND $_.CanStop. (I will talk about this in a little bit.) Where is an alias for the Where-Object cmdlet. The question mark (?) is also an alias for the Where-Object cmdlet. But notice that in the script block of the Where-Object cmdlet, condition one is equal to Running, AND condition two exists.

What is a Boolean value, and what do I do about it?

In the get running services that also accept a Stop command event, there is a property called CanStop that exists on the System.ServiceProcess.ServiceController object. This CanStop property is shown here.

PS C:\Users\ed.IAMMRED> Get-Service | Get-Member canstop

   TypeName: System.ServiceProcess.ServiceController

Name    MemberType Definition

----    ---------- ----------

CanStop Property   System.Boolean CanStop {get;}

Notice that under the definition, it says that the CanStop property is a System.Boolean. Boolean values are On/Off, True/False, -1/0, Enabled/Disabled, Exist/Don’t exist, and so on. One of the cool things about Windows PowerShell, is that if something exists, and it is a Boolean value, I only need to say Where plus ThePropertyOfTheObject. Let’s go back to Beginner Event 3. So, in the long version of the Where clause, the writer had the following:

where {($_.status -eq "Running") -and ($_.CanStop -eq $true)}

As I mentioned, it works. But it can be shortened by saying –and $_.CanStop. Here we are saying, “Does the CanStop property exist on the object?” If it does, this is all we want. Therefore, I can reduce this by saying, “Does it exist?” This is shown here.

where { $_.status -eq 'running' -and $_.canstop }

The nice thing is that I can negate the Boolean value. For example, if I want the running services that do NOT accept a Stop command, I have several choices. I would probably use the Not operator (!). This is shown here.

where { $_.status -eq 'running' -AND !$_.canstop }

Most of the time, I will use grouping around the Boolean value to make the code a bit clearer. This is shown here.

where { $_.status -eq 'running' -AND !($_.canstop)

If you do not like the exclamation point (!) for the Not operator, you also can use the –not operator. This is shown here.

where { $_.status -eq 'running' -AND -not $_.canstop }

Once again, I generally put grouping around the Boolean value to make the command easier to read (at least for me). This is shown here.

where { $_.status -eq 'running' -AND -not ($_.canstop) }

Not handling the default

I also saw lots of scripts that had a hardcoded value for ComputerName. Some said Server01, others said NameOfRemoteServer. I am calling this out because it is a problem. It causes someone to immediately edit the script before they can run it. I wrote a really good blog about this issue that I will refer you to: Create and Use Default Values in PowerShell Scripts.

Well, that is about it for now. There are thousands of scripts that need to be graded in the next few days. So until tomorrow, have a great day.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • You could strip that down to where {$_.canstop}. A service that isn't running wont return true.

  • @Andrew morgan that is absolutely true. In Windows 7, when using the .NET Framework classes (which Get-Service uses) a running service will return canstop IF the service will handle a stop command. A non-running service does not return anything for the canstop property.

  • I guess I am confused about the compound where paragraph.  If "where {($_.status -eq "Running") -and ($_.CanStop -eq $true)}" works just fine, and it seems to be a compound where clause to me, what is the advantage of "where { $_.status -eq 'running' -and $_.canstop }"?

    I mean is there a performance reason or is the only advantage less typing?

  • @Neil: While the first example you show would work, the reason why 'Where {$_.status -eq 'running' -And $_.CanStop} is better is because $_.CanStop is already $True, so performing a comparison ($_.CanStop -eq $True) to evaluate it to $True is redundant.

  • @Andrew & @IamMred that was my point in my event 3 submission however a judge gave me only one star saying that I wasn't finding running services.

    Not bitter or anything, just happy that this clears up the issue :-)

  • One thing that always gets me is "Get-Something | ? {$_ -eq 4 -or 5}". I always have to remember todo "Get-Something | ? {($_ -eq 4) -or ($_ -eq 5)}". The first one reads normal but the second one is easier to understand, that's my opinion.

  • @dsvdweeb

    You can have your clause and read it too:

    3..8 | ? {4,5 -contains $_}

    PowerShell always has tricks up its sleeve.