PowerShell lets you create compact, highly portable scripts, but if you don't have much experience using command-line tools, getting PowerShell to find those scripts when you use them can be a problem. Unlike Cmd.exe, in PowerShell the current shell location is not in the command search path, so you can't simply save files to an arbitrary folder and set that as the working location. This is a crucial point, which I’ll explain in more detail momentarily. Now, let’s walk through exactly how command search works and discuss how the command-search process is very different from working with graphical applications. Then I'll show you the techniques available for making PowerShell find external tools correctly.
How PowerShell Command Search Works Let me start out by defining a special term I use for scripts and applications that reside in files: I call them command files. Command search is all about finding command files by their name.
The Windows GUI doesn't have a precise analog to the concept of a command-search path. Normally, you run an application by clicking an icon. That icon either points directly to an application in a known, specific location or to a document that has a document handler application in a known, specific location. In the case of documents, Windows has to look up the application location in the registry, but for all practical purposes, this is deterministic; it isn't a search.
When you give PowerShell a name it interprets as a command, however, something a little different happens. PowerShell is able to precisely identify the command if it is a function, filter, or cmdlet name already loaded into PowerShell; if this is the case, then there is no search process. If the command name isn't one of these internal command types, PowerShell then checks to see if what you entered looks like an explicit command path, such as c:\windows\notepad.exe or \someapp.exe. If the entered command isn't an explicit command path, PowerShell searches for the command. (If you're wondering why I don't mention aliases, it's because PowerShell resolves aliases to the command name used in the alias definition; they're not true command names). Let's say we enter asdf.x, which won't be a command or document name on your system; this forces PowerShell to go through the entire command-search process.
PowerShell's first step in looking for the command is to check the environment path variable. This variable contains a semicolon-separated list of directories where PowerShell will look for the command. You can see this set of directories if you type $env:path at a PowerShell prompt. If your path variable contains the string
C:\Windows\system32;C:\Windows;C:\Windows\system32\wbem; C:\Windows\System32\WindowsPowerShell\v1.0\
then PowerShell will check the following directories in the order shown:
C:\Windows\system32 C:\Windows C:\Windows\system32\wbem C:\Windows\System32\WindowsPowerShell\v1.0
PowerShell begins the search in C:\Windows\System32 by checking for a file with the exact name asdf.x. If there were a file C:\Windows\System32\ asdf.x, then PowerShell would pass the request to open the file over to Windows Explorer. When Windows Explorer can't find a document handler for .x files, Explorer automatically prompts you to find the application to use to open .x files. PowerShell can't control the prompting for a document handler.
Assuming there was no asdf.x file, PowerShell next assumes that you might have entered the name for an executable file, but without the executable file extension. Here we're using the term executable in a broad sense: PowerShell treats files as executables if they end in .PS1 (for PowerShell scripts) or any of the extensions found in the pathext environment variable. If you enter $env:pathext at a PowerShell prompt, you'll a list, which should look something like this:
.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;PSC1
PowerShell next checks for the file C:\Windows\System32\asdf.x.PS1. If that's not found, PowerShell tries each of the types in pathext in turn, asking the directory C:\Windows\System32 if it contains asdf.x.COM, asdf.x.EXE, asdf.x.BAT, asdf.x.CMD, asdf.x.VBS, asdf.x.VBE, asdf.x.JS, asdf.x.JSE, asdf.x.WSF, asdf.x.WSH, asdf.x.MSC, and finally asdf.x.PSC1.
If PowerShell hasn't found a file at this point, it goes on to the next directory, C:\Windows, and starts the matching process again. When PowerShell finishes checking the last directory for the last possible match and still hasn't succeeded, it makes one more search attempt. Here's how it works.
If the command name you used didn't begin with "get-", PowerShell considers the possibility that you might be using a short name for a "getter" script. PowerShell now repeats the entire process using the base name get-asdf.x. If this still doesn't produce a matching name, PowerShell gives you the error message The term 'asdf.x' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
We can summarize the logic behind the process. Roughly, in crude pseudo-code, it's this: "If a term X is not a PowerShell internal command, and is not a precise path to an external file, then, for each directory listed in $env:path do the following and return the first match: Check the directory for a file named X; then check for a file X.PS1; then check for X with each extension listed in $env:pathext appended in turn."
You can actually generate a demonstration easily with the Show-CommandResolution script in Listing 1.

If you run Show-CommandResolution with a name as an argument, the script automatically generates all of the possible attempted name matches for the current system configuration. In other words, you can get different results on different systems, but the results are always correct. Try typing a line such as
Show-CommandResolution asdf.x
to see for yourself. You can also use Get-Command to show all of the real files that match a particular name; Get-Command also shows PowerShell internal commands. For example, typing
Get-Command sc
will show you that sc matches an alias mapped to Set-Content as well as the Service Controller application, sc.exe.
This raises an interesting point: Conflicts can and will occur between names of various applications. So it's useful to be aware of how command resolution works and to check names with Get-Command if you think there's a problem.
Why Command Search is Do-it-Yourself Now that we've looked at how command search works, it will be somewhat easier to understand why applications you install don't automatically provide working command search.
It all starts with the way that application installation has developed since the advent of the Windows GUI. Application installation procedures have been heavily oriented towards supporting the mass of consumer applications and remediating problems with those installations. These applications are generally graphical standalone applications that are immersive: You don't directly chain together the applications yourself. To reduce the likelihood of application installations causing problems for other applications, applications must be installed in their own distinct directories. There is minimal support for analyzing and working with the path during application installations—since path search can cause significant problems for mixed-version DLLs, this is by design as well.
Unfortunately, this lack of support for path search leaves command-line tool users out in the cold. You could write an installer routine for each application that adds the application install directory to your search path, but that's very unattractive. Command-line tools are generally a la carte items, and not only would separate installations produce a lot of extra effort and packaging for something that's supposed to be a drop-in component, but they'd produce enormous search paths. I have roughly 600 command-line tools and scripts that I use occasionally; adding a path such as C:\Program Files\Developer Company\ToolName\ to my search path for each one would cause the search path to balloon and command search to slow down to a crawl.
An ideal solution would be to use a common tools folder that supports single-tool, drag-and-drop or built-in self-installer scripting, but there's no such thing on the Windows platform. Although you can create your own tools folder, which is the best general solution to helping PowerShell find tools, it's not as satisfactory a solution as it could be.
For comparison, consider the UNIX family of OSs. They not only come with a wide range of command-line tools preinstalled, they also have a standard directory for adding small executables: the /usr/bin folder. Tools generally come with an installer script that handles placing them there, and even programs distributed as source files include installation as part of the make script. You don't need to be aware of any of these details to add new applications—you just need to run the install script. If you get a precompiled tool with no installer, you just need to know about /usr/bin; all you do is place the tool there and tell UNIX it's an executable.
In contrast, on Windows you're on your own. You need to understand that you must manually place executables in your search path. You decide where to put a tools folder and then create it. You set permissions appropriately on the folder. Then you epeat this process manually for each and every system where you need to use the tool or tools you're going to install. Finally, you put the tools in that location. It's easy to describe, but particularly if you're just starting to use command-line tools, it involves both decision-making and time.