Check if an executable exists in the Windows path

If I run a process with ShellExecute (or in .net with System.Diagnostics.Process.Start()) the filename process to start doesn't need to be a full path.

If I want to start notepad, I can use

Process.Start("notepad.exe");

instead of

Process.Start(@"c:\windows\system32\notepad.exe");

because the direcotry c:\windows\system32 is part of the PATH environment variable.

how can I check if a file exists on the PATH without executing the process and without parsing the PATH variable?

System.IO.File.Exists("notepad.exe"); // returns false
(new System.IO.FileInfo("notepad.exe")).Exists; // returns false

but I need something like this:

System.IO.File.ExistsOnPath("notepad.exe"); // should return true

and

System.IO.File.GetFullPath("notepad.exe"); // (like unix which cmd) should return
                                           // c:\windows\system32\notepad.exe

Is there a predefined class to do this task available in the BCL?


I think there's nothing built-in, but you could do something like this with System.IO.File.Exists:

public static bool ExistsOnPath(string fileName)
{
    return GetFullPath(fileName) != null;
}

public static string GetFullPath(string fileName)
{
    if (File.Exists(fileName))
        return Path.GetFullPath(fileName);

    var values = Environment.GetEnvironmentVariable("PATH");
    foreach (var path in values.Split(Path.PathSeparator))
    {
        var fullPath = Path.Combine(path, fileName);
        if (File.Exists(fullPath))
            return fullPath;
    }
    return null;
}

This is risky, there's a lot more to it than just searching the directories in the PATH. Try this:

 Process.Start("wordpad.exe");

The executable is stored in c:\Program Files\Windows NT\Accessories on my machine, that directory is not on the path.

The HKCR\Applications and HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths keys also play a role in finding executables. I'm fairly sure there are additional land-mines like this around, directory virtualization in 64-bit versions of Windows could trip you up for example.

To make this more reliable I think you need to pinvoke AssocQueryString(). Not sure, never had the need. The better approach is certainly to not have to ask the question.


Ok, a better way I think...

This uses the where command, which is available at least on Windows 7/Server 2003:

public static bool ExistsOnPath(string exeName)
{
    try
    {
        using (Process p = new Process())
        {
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.FileName = "where";
            p.StartInfo.Arguments = exeName;
            p.Start();
            p.WaitForExit();
            return p.ExitCode == 0;
        }
    }
    catch(Win32Exception)
    {
        throw new Exception("'where' command is not on path");
    }
}

public static string GetFullPath(string exeName)
{
    try
    {
        using (Process p = new Process())
        {
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.FileName = "where";
            p.StartInfo.Arguments = exeName;
            p.StartInfo.RedirectStandardOutput = true;
            p.Start();
            string output = p.StandardOutput.ReadToEnd();
            p.WaitForExit();

            if (p.ExitCode != 0)
                return null;

            // just return first match
            return output.Substring(0, output.IndexOf(Environment.NewLine));
        }
    }
    catch(Win32Exception)
    {
        throw new Exception("'where' command is not on path");
    }
}