Check if an executable exists in the Windows path

后端 未结 8 1664
栀梦
栀梦 2020-11-29 06:39

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

相关标签:
8条回答
  • 2020-11-29 06:43

    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.

    0 讨论(0)
  • 2020-11-29 06:46

    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;
    }
    
    0 讨论(0)
  • 2020-11-29 06:46

    I combined the answers by @Ron and @Hans Passant to create a class that checks for the file path in both App Path registry key, and in PATH by calling PathFindOnPath. It also allows to omit the file extension. In such cases, it probes for several possible "executable" file extensions from PATHEXT.

    How to use:

    CommandLinePathResolver.TryGetFullPathForCommand("calc.exe"); // C:\WINDOWS\system32\calc.exe
    
    CommandLinePathResolver.TryGetFullPathForCommand("wordpad"); // C:\Program Files\Windows NT\Accessories\WORDPAD.EXE
    

    Here is the code:

    internal static class CommandLinePathResolver
    {
        private const int MAX_PATH = 260;
        private static Lazy<Dictionary<string, string>> appPaths = new Lazy<Dictionary<string, string>>(LoadAppPaths);
        private static Lazy<string[]> executableExtensions = new Lazy<string[]>(LoadExecutableExtensions);
    
        public static string TryGetFullPathForCommand(string command)
        {
            if (Path.HasExtension(command))
                return TryGetFullPathForFileName(command);
    
            return TryGetFullPathByProbingExtensions(command);
        }
    
        private static string[] LoadExecutableExtensions() => Environment.GetEnvironmentVariable("PATHEXT").Split(';');
    
        private static Dictionary<string, string> LoadAppPaths()
        {
            var appPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    
            using var key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\App Paths");
            foreach (var subkeyName in key.GetSubKeyNames())
            {
                using var subkey = key.OpenSubKey(subkeyName);
                appPaths.Add(subkeyName, subkey.GetValue(string.Empty)?.ToString());
            }
    
            return appPaths;
        }
    
        private static string TryGetFullPathByProbingExtensions(string command)
        {
            foreach (var extension in executableExtensions.Value)
            {
                var result = TryGetFullPathForFileName(command + extension);
                if (result != null)
                    return result;
            }
    
            return null;
        }
    
        private static string TryGetFullPathForFileName(string fileName) =>
            TryGetFullPathFromPathEnvironmentVariable(fileName) ?? TryGetFullPathFromAppPaths(fileName);
    
        private static string TryGetFullPathFromAppPaths(string fileName) =>
            appPaths.Value.TryGetValue(fileName, out var path) ? path : null;
    
        private static string TryGetFullPathFromPathEnvironmentVariable(string fileName)
        {
            if (fileName.Length >= MAX_PATH)
                throw new ArgumentException($"The executable name '{fileName}' must have less than {MAX_PATH} characters.", nameof(fileName));
    
            var sb = new StringBuilder(fileName, MAX_PATH);
            return PathFindOnPath(sb, null) ? sb.ToString() : null;
        }
    
        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
        private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[] ppszOtherDirs);
    }
    
    0 讨论(0)
  • 2020-11-29 06:51

    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");
        }
    }
    
    0 讨论(0)
  • 2020-11-29 06:52

    I tried out Dunc's "where" process and it works, but it's slow and resource-heavy and there's the slight danger of having an orphaned process.

    I like Eugene Mala's tip about PathFindOnPath, so I fleshed that out as a complete answer. This is what I'm using for our custom in-house tool.

    /// <summary>
    /// Gets the full path of the given executable filename as if the user had entered this
    /// executable in a shell. So, for example, the Windows PATH environment variable will
    /// be examined. If the filename can't be found by Windows, null is returned.</summary>
    /// <param name="exeName"></param>
    /// <returns>The full path if successful, or null otherwise.</returns>
    public static string GetFullPathFromWindows(string exeName)
    {
        if (exeName.Length >= MAX_PATH)
            throw new ArgumentException($"The executable name '{exeName}' must have less than {MAX_PATH} characters.",
                nameof(exeName));
    
        StringBuilder sb = new StringBuilder(exeName, MAX_PATH);
        return PathFindOnPath(sb, null) ? sb.ToString() : null;
    }
    
    // https://docs.microsoft.com/en-us/windows/desktop/api/shlwapi/nf-shlwapi-pathfindonpathw
    // https://www.pinvoke.net/default.aspx/shlwapi.PathFindOnPath
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
    static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[] ppszOtherDirs);
    
    // from MAPIWIN.h :
    private const int MAX_PATH = 260;
    
    0 讨论(0)
  • 2020-11-29 07:01

    Much shorter and direct, which is what the poster wanted.

    FILE *fp
    char loc_of_notepad[80] = "Not Found";
    
    // Create a pipe to run the build-in where command
    // It will return the location of notepad
    fp = popen("cmd /C where notepad", "r");
    // Read a line from the pipe, if notepad is found 
    // this will be the location (followed by a '\n')
    fgets(loc_of_notepad, 80, fp);
    fclose(fp);
    
    printf("Notepad Location: %s", loc_of_notepad);
    
    0 讨论(0)
提交回复
热议问题