The existing answers are helpful, but I think a more systematic discussion is helpful too.
tl;dr
where
is PowerShell's built-in alias for the Where-Object
cmdlet; to invoke the external where.exe
program, use .exe
explicitly:[1]
# Note the use of '.exe' to disambiguate the external 'where.exe' program
# from PowerShell's built-in 'where' alias (for 'Where-Object').
PS> where.exe git
C:Program FilesGitcmdgit.exe
where.exe
, whose purpose is return the full path of an executable in the system's path (in one of the directories listed in the $env:PATH
environment variable), is unrelated to cmd
(the legacy command processor): it is an external executable that comes with Windows, and it can be invoked from any shell, and therefore also from PowerShell.
By contrast, cmd
does have so-called internal commands that indeed can only be called from cmd
, such as mklink
; in fact, in cmd
you can use where <name>
to infer whether a given (functioning) command <name>
is internal or not: if there's no output, the command is internal (or doesn't exist at all).
Alternatively, use the equivalent and more flexible PowerShell counterpart to where.exe
, the Get-Command
cmdlet; it returns System.Management.Automation.CommandInfo
instances (or instances of derived classes), whose .Source
property contains the full path for a command-info object representing an external executable:
PS> (Get-Command git).Source
C:Program FilesGitcmdgit.exe
Note:
where.exe
finds only executable files, whereas Get-Command
by default looks for all command types (aliases, functions, cmdlets, ...) - see next section.
Unlike Get-Command
, where.exe
also finds executables located in the current directory. Get-Command
doesn't do that, because PowerShell by design, for security reasons, doesn't allow calling executables located in the current directory by name only - a path is required (e.g., .foo
).
PowerShell has different types of commands, which - in the case of name conflicts - have a predefined order of precedence to determine what type should be the effective command.
That is, if a given command name matches two or more commands, it is their type that determines which command is actually invoked.
This precedence is documented in the conceptual about_Command_Precedence
help topic; in short, here is the command precedence by type in descending order (highest precedence first):
- aliases
- function
- cmdlets (loosely speaking: functions implemented as compiled binaries)
- external executables, including
*.ps1
script files - see bottom section
An easy way to see what command types exist for a given name is to add the -All
switch when calling the Get-Command
cmdlet, which lists the matching commands in descending order of precedence; that is, the command that will actually be executed via the given name is listed first.
PS> Get-Command -All where
CommandType Name Version Source
----------- ---- ------- ------
Alias where -> Where-Object
Application where.exe 10.0.18... C:WINDOWSsystem32where.exe
The result shows that the built-in where
alias for the Where-Object
cmdlet (whose purpose is to filter pipeline input) is the effective command when you submit where
, and not the desired where.exe
executable.
Given that the where.exe
executable file name has the .exe
extension that can distinguish it from the where
alias, it is simplest to invoke where.exe
with the filename extension, as shown at the top.
In cases where this is not possible (e.g., on Unix-like platforms, where executables typically do not have a filename extension or if an alias shadows a function), you can use the -Type
parameter to get the command of interest, and invoke it with &
, the call operator:
# Invokes where.exe, as only it is of type 'Application' (external executable)
& (Get-Command -Type Application where) git
Should there be multiple external executables whose base file name is where
, it is the one from the directory listed earliest in $env:PATH
that will be executed - see next section.
Precedence among external executables and *.ps1
scripts:
Note:
One important difference between cmd
and PowerShell is that PowerShell - by design, for security reasons - does not allow you to invoke an external executable or .ps1
script located in the current directory by name only; to do so, you must use a path, in the simplest case by prepending .
(or ./
); e.g., to invoke an executable foo
located in the current directory, you must use ./foo ...
The precedence between *.ps1
scripts and other executables in effect differs by platform (Windows vs. Unix-like platforms), as detailed below.
The following discussion assumes that a given command name isn't shadowed by higher-precedence command types, such as aliases, and resolves to an external executable or *.ps1
script.
Precedence rules:
When a command name resolves to potentially multiple external executables or *.ps1
scripts via the directories listed in the $env:PATH
environment variable, the executable / script located in the directory that is listed earliest is invoked.
If, in that earliest directory:
the given name exactly matches an executable file name (e.g., where.exe
) or script (e.g., foo.ps1
), there is no ambiguity, and that executable / script is invoked.
the given name doesn't include a filename extension (e.g., foo
), multiple executables can match (via implied filename extensions), and the one to actually invoke is determined as follows:
Note:
The precedence rules among multiple executables in a given directory also apply when using an explicit path (without a filename extension); e.g., invoking ./foo
decides the precedence among multiple executables in the current directory whose base name is foo
as described above.
Placing .ps1
scripts in a directory listed in $env:PATH
and invoking them by (base) name only isn't all that common, although it is worth considering as an alternative to putting potentially many functions in one's $PROFILE
file.
- Unfortunately, the UX is poor on Linux, where, due to its case-sensitive file system, you must specify the (base) file name case-exactly on invocation, whereas PowerShell command invocation is otherwise case-insensitive; e.g., if the actual file name is
Get-Foo.ps1
, only Get-Foo
works for invocation, not get-foo
.
[1] As for why a call such as where git
- i.e. mistaken use of Where-Object
-