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 Files\Git\cmd\git.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 Files\Git\cmd\git.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:\WINDOWS\system32\where.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 - doesn't produce output: The call is equivalent to Where-Object git, which is in turn equivalent to Where-Object { $_.git }, which, in the absence of pipeline input to the cmdlet by definition never produces output.