Get-Command and where.exe disagree about which npm exists, and both hand you an unrunnable path
aliases · case
Symptom
Section titled “Symptom”You ask PowerShell where a tool lives, spawn what it tells you, and get an error code you have never seen. Ask a different resolver and you get a different path, which fails a different way. Neither answer is runnable.
Get-Command npm -> C:\Program Files\nodejs\npm.ps1 -> spawn EFTYPEwhere.exe npm -> C:\Program Files\nodejs\npm -> spawn ENOENTMeanwhile npm --version typed into the same prompt works perfectly.
(Get-Command npm).Source # C:\Program Files\nodejs\npm.ps1(Get-Command npm).CommandType # ExternalScript
where.exe npm# C:\Program Files\nodejs\npm# C:\Program Files\nodejs\npm.cmdThe two resolvers do not even agree on which files exist:
Get-Command npm -All | ForEach-Object { $_.CommandType.ToString() + " -> " + $_.Source }# ExternalScript -> ...\npm.ps1 <- where.exe never lists this# Application -> ...\npm.cmd# Application -> ...\npmGet-Command ranks .ps1 first and where.exe omits it entirely, because
one enumerates PowerShell command types and the other walks PATHEXT. Whichever
one your detection code calls determines which way you fail.
Spawn results for all three candidates
Section titled “Spawn results for all three candidates”| what resolved it | path | spawnSync result |
|---|---|---|
Get-Command (rank 1) |
npm.ps1 |
EFTYPE |
where.exe (rank 1) |
npm (extensionless) |
ENOENT |
| neither, by hand | npm.cmd |
works — see below |
EFTYPE is the interesting one: it is not in most people’s mental error table,
so it reads as “corrupt binary” rather than “this is a script that needs an
interpreter”. The extensionless file is a POSIX sh script, hence ENOENT from
CreateProcess.
The .cmd path has one more trap
Section titled “The .cmd path has one more trap”Picking .cmd is correct, but the obvious ComSpec invocation still fails when
the path contains a space:
spawnSync(process.env.ComSpec, ["/d","/s","/c", `"${target}" --version`], { windowsVerbatimArguments: true });// exit 1// stderr: 'C:\Program' is not recognized as an internal or external commandcmd /s /c strips the outer pair of quotes from the entire command line, so
your quotes around the path are the ones removed. Wrap twice:
spawnSync(process.env.ComSpec, ["/d","/s","/c", `""${target}" --version"`], { windowsVerbatimArguments: true });// exit 0// stdout: 10.9.2Workaround
Section titled “Workaround”# ask for Application only, and prefer .cmd/.exe explicitlyGet-Command npm -All | Where-Object { $_.CommandType -eq 'Application' -and $_.Source -like '*.cmd' } | Select-Object -First 1 -ExpandProperty SourceIn spawn logic: never trust rank 1 from either resolver. Enumerate all
candidates, filter to .exe then .cmd, reject .ps1 and extensionless, and
double-quote the ComSpec command line.
npm-ps1-not-comspec establishes that the .ps1 shim wins the PATH race and
cannot be launched. This case adds the part that makes it hard to diagnose: the
two resolvers you would use to investigate disagree about which files exist,
so the tool you reach for decides which error you see. It also records EFTYPE
as the concrete spawn code, and the cmd /s /c double-quoting requirement, none
of which appear in the archive.
Environment
Section titled “Environment”$PSVersionTable: 5.1.26100.7705, Edition Desktop, Windows 11, Node 22.14,
npm 10.9.2.