Use a PowerShell Function to See If a Command Exists

Use a PowerShell Function to See If a Command Exists

  • Comments 5
  • Likes

Summary: Microsoft Scripting Guy, Ed Wilson, writes a Windows PowerShell function to see if a command exists before calling the command.

Microsoft Scripting Guy, Ed Wilson, is here. This Thursday, February 21, 2013, I will be on the PowerScripting Podcast. I always enjoy talking with Jon and Hal, and while I am not sure of the actual agenda, I do know that we will be talking about my new Windows PowerShell 3.0 Step by Step book. We might talk about the 2013 Scripting Games, Windows PowerShell Saturday, TechEd, or other things I am doing at the Microsoft TechNet Script Center, and of course, the Hey, Scripting Guy! Blog. Other items related to the Windows PowerShell community will doubtlessly arise. One of the really cool things about the PowerScripting Podcast is the chat room. Lots of cool questions come up via the chat room, and at times, there are even funny comments, and it is all I can do to keep from busting out laughing (in fact, sometimes I do end up laughing …) It is like a virtual family reunion (actually better than a virtual family reunion because at least we all have something in common—Windows PowerShell).

Note   My Mentee (or protégé, or Telemachus) Microsoft PFE Ashley McGlone has written an excellent 4-page guide that shows how to use Windows PowerShell to replace RepAdmin, DNSCMD, DCPromo, CSVDE, NETDOM, NLTEST and so on and so on and so on. This is a most excellent reference and is the vision of Windows PowerShell—replace nearly two dozen command-line utilities, each with its own confusing and different syntax, with the consistency of Windows PowerShell. It is way cool and should really help to simplify your life. You can download it from his blog here.

Finding required commands in Windows PowerShell

In a recent PowerTip, I talked about using the Get-ADReplicationConnection cmdlet to find information about AD DS replication. At the time, I wanted to confirm if the cmdlet was available by determining which version of the ActiveDirectory module is installed. Unfortunately, as I checked around, the only way I had to confirm this was to go to a computer with the specific version of the ActiveDirectory module. I had hoped I could check the version property of the module, but unfortunately, the AD DS team did not increment the version number of the module. This is shown here:

PS C:\> ipmo activedirectory

PS C:\> (Get-Module activedirectory).version

 

Major  Minor  Build  Revision

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

1      0      0      0

This leaves me with the situation in which if I want to see if a cmdlet (or a function) exists, I can attempt to use it, and generate an error if the command does not exist. Or I can use the Get-Command cmdlet to search for the cmdlet (or function), but once again, it generates an error if the command does not exist.

So, I decided to write my own function to see if a command exists.

The Test-CommandExists function

To keep from generating an error when I call the Get-Command cmdlet, I use Try / Catch / Finally. There is one problem, and that is that a command not found is not a terminating error, and Try / Catch / Finally only handles terminating errors. Terminating errors are errors that are so bad that Windows PowerShell cannot do anything but halt executing. A non-terminating error is bad, but Windows PowerShell will, by default, continue to the next command. To change this behavior, I need to change the $ErrorActionPreference value from the default value of continue to stop. In this way, the non-terminating error will cause Windows PowerShell to halt execution, therefore permitting the Try / Catch / Finally block to handle the error condition.

But, changing a preference variable without asking is just rude (and is not considered to be a best practice), so, I am going to only change the preference variable while my function runs, and I will change it back when I am done. This is the great thing about Try / Catch / Finally—that I can specify code for each step of the process.

Note   The Finally ALWAYS runs, and it is a perfect place to set things back in their proper condition. I always put my cleanup code in the Finally block.

Therefore, one of the first things I do in my function is store the current $ErrorActionPreference value and then I assign the value of stop to $ErrorActionPreference. These two lines of code are shown here.

$oldPreference = $ErrorActionPreference

$ErrorActionPreference = 'stop'

I create an input parameter that permits me to specify the command I am interested in when I call the function. This line is shown here.

Param ($command)

I use Try to see if the command exists. To do this, I use the If language statement, and I evaluate the condition occurring when I use Get-Command to look for the command. If the command exists, I display this information. The line of code is shown here.

try {if(Get-Command $command){"$command exists"}} 

Now I use Catch to catch the error condition. Therefore, if Get-Command $command results in an error, the code progresses to Catch, and I display a message stating the command does not exist. This is shown here.

Catch {"$command does not exist"}

If an error occurs, the Catch statement displays a message. The code continues to the Finally block. But if an error does not occur, the Catch statement does not execute. However, the Finally block still executes because FINALLY ALWAYS runs. In the Script Block associated with Finally, I set $ErrorActionPreference back to the original value—the value I stored when the function began. This code is shown here.

Finally {$ErrorActionPreference=$oldPreference}

The complete Test-CommandExists function is shown here.

Function Test-CommandExists

{

 Param ($command)

 $oldPreference = $ErrorActionPreference

 $ErrorActionPreference = 'stop'

 try {if(Get-Command $command){"$command exists"}}

 Catch {"$command does not exist"}

 Finally {$ErrorActionPreference=$oldPreference}

} #end function test-CommandExists

When I run the function, the following output is shown.

Image of function output

Making the Test-CommandExists function more friendly

If I configure the function to return Boolean values, instead of strings, I can use the If statement when calling the code. Here is the revision to the function:

Function Test-CommandExists

{

 Param ($command)

 $oldPreference = $ErrorActionPreference

 $ErrorActionPreference = 'stop'

 try {if(Get-Command $command){RETURN $true}}

 Catch {Write-Host "$command does not exist"; RETURN $false}

 Finally {$ErrorActionPreference=$oldPreference}

} #end function test-CommandExists

I do not need to add the RETURN key word, but it makes the code easier to read. The Try section is basic. It is the Catch line where I had to get creative. If I have the Catch section return $false, nothing displays when I call the function as seen here.

If(Test-CommandExists Get-myService){Get-myService}

But, on the other hand, if I have a string that states the command does not exist, it is evaluated as $true and the code attempts to run, and an error generates. Therefore, I decided to use Write-Host here because it does not return to the output stream, and therefore, the calling code does not see it. The use of this function is shown here.

Image of function output

Join me tomorrow when I will talk about more cool Windows PowerShell stuff.

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
  • I imagine more validation can be added, but here is what came out with. I use a regular expression to match. Providing Get-Command with a verb and noun performs matching that does not return an error.

    function Test-Command {

       <#

           .SYNOPSIS

               Determines if the command is available.

           .EXAMPLE

               PS C:\> Test-Command -Command Get-Process

               True

           .EXAMPLE

               PS C:\> Test-Command -Command Do-Something

               False

       #>

       param($Command)

       $found = $false

       $match = [Regex]::Match($Command, "(?<Verb>.{3,11})-(?<Noun>.{3,})")

       if($match.Success) {

           if(Get-Command -Verb $match.Groups["Verb"] -Noun $match.Groups["Noun"]) {

               $found = $true

           }

       }

       $found

    }

    I limit the verb 3-11 characters because this command shows that the approved verbs are within that range.

    Get-Verb | Select-Object @{n="Length";e={$_.Verb.Length}} | Sort-Object -Property Length | Format-Table -AutoSize

  • @MichaelLWest excellent. I love your function. Well done.

  • Wouldn't that be easier?

    [bool](Get-Command $command -ea 0)

  • @Rood indeed it would. This is a great trick, and one that I had not thought of before. Awesome!

  • @Rood, nice.

    Saw an article about it here:

    blogs.msdn.com/.../erroraction-silentlycontinue-ea-0.aspx