Checking if an executable exists in PATH using Node.js

I was looking for a way to distribute a Powershell script. I decided to wrap it inside an npm package and let users run the Powershell script using:

npx package-name [...args]

which passes arguments to Powershell and let it run the actual script, which is bundled with the npm package.


The problem arised when I wanted to check if a modern version of Powershell exists on the user's machine. The first solution that came to my mind was checking executable version[1].

pwsh -command "echo $PSVersionTable.PSVersion.ToString()" 

but it runs slowly. It also doesn't handle commands without --version or any command switch at all. So trying to run the program to see if actually exists is not a great way to go about it.


A faster solution is to check if pwsh.exe exists in PATH. There's a pre-made solution for this, as with anything, called hasbin but it's quite old and uses external dependencies for async operations that are native to modern JS.

findExecutable function builds a list of candidates paths from the combinations of all directories in PATH and all extensions (no-op if not on Windows) and checks if any of them exists and returns the first result.

If I were to look for pwsh.exe and PATH were a/b;c/d, it would try a/b/pwsh.exe, c/d/pwsh.exe and so on.

const path = require("path");
const fs = require("fs/promises");

 * @param {string} exe executable name (without extension if on Windows)
 * @return {Promise<string|null>} executable path if found
 * */
async function findExecutable(exe) {
    const envPath = process.env.PATH || "";
    const envExt = process.env.PATHEXT || "";
    const pathDirs = envPath
        .replace(/["]+/g, "")
    const extensions = envExt.split(";");
    const candidates = pathDirs.flatMap((d) => => path.join(d, exe + ext))
    try {
        return await Promise.any(;
    } catch (e) {
        return null;

    async function checkFileExists(filePath) {
        if ((await fs.stat(filePath)).isFile()) {
            return filePath;
        throw new Error("Not a file");

We can finally use findExecutable to get the most recent Powershell installation.

async function getPowershellVariant() {
    if (await findExecutable("pwsh")) {
        return "pwsh";
    if (await findExecutable("powershell")) {
        return "powershell";

    throw new Error("Powershell is not installed, or not in PATH");

What is PATHEXT for?

Windows uses PATHEXT environment variable to determine the file name for an executable if you don't specify an extension. On my PC, PATHEXT content is:


That means, if I just type pwsh, it looks for, pwsh.exe, pwsh.bat and so on, and executes the first one it finds. That also means if an extension is not on the list, I have to type in the extension.

This is not an issue on UNIX systems, because they use an executable bit +x to specify if a file can be executed, regardless of whether it has an extension or not.

  1. This usually involves running a binary with --version option, but Powershell behaves completely differently and uses -version option to specify the version of Powershell to run the command/script against. ↩︎

Last updated: