【问题标题】:Check if an executable exists in the Windows path检查 Windows 路径中是否存在可执行文件
【发布时间】:2010-10-04 14:01:16
【问题描述】:

如果我使用ShellExecute(或在.net 中使用System.Diagnostics.Process.Start())运行进程,则启动的文件名进程不需要是完整路径。

如果我想启动记事本,我可以使用

Process.Start("notepad.exe");

而不是

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

因为目录 c:\windows\system32 是 PATH 环境变量的一部分。

如何在不执行进程且不解析 PATH 变量的情况下检查 PATH 上是否存在文件?

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

但我需要这样的东西:

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

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

BCL 中是否有预定义的类来执行此任务?

【问题讨论】:

  • 虽然这样一个预定义的类会很方便(或者很方便,如果它存在的话)是不是只需要多一行来获取路径然后检查存在()?你可以比问问题更快地写出来。特殊原因/需要?只是想知道。
  • 是的,应该很容易。但我的信念是,如果可以使用现有的编程语言库完成一项任务,我更喜欢这种方式,而不是一次又一次地重新发明轮子。如果没有可用的东西,我自己做。
  • @MickeyfAgain_BeforeExitOfSO 解析 PATH 是特定于平台的(例如,/ vs \ 用于子目录,: vs ; 用于条目分隔符),以及希望始终处理模棱两可的结果 - 所以它不像“获取路径然后检查存在()”那么简单。此外,如果有几十个或要检查的路径,或者如果有任何是远程/网络路径,那么例如检查程序代码而不是具有预缓存结果的操作系统可能会对性能产生重大影响。等等等等。

标签: c# .net file


【解决方案1】:

我认为没有任何内置功能,但您可以使用 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;
}

【讨论】:

  • 如果你要这样做,我建议把这些变成扩展方法...msdn.microsoft.com/en-us/library/bb383977.aspx
  • @Aaron: 你确定你会看到GetFullPath 作为string 的扩展方法吗?对我来说这听起来很奇怪......也许对FileInfo 有意义......
  • 是的,使用字符串会很奇怪。但是我认为将上述功能从这两种方法包装到一个名为 ExistsOnPath 的扩展方法中是有意义的,正如你提到的那样,它挂在 FileInfo 上。
  • @Aaron:出于几个原因(例如,如果我只需要一个字符串,为什么我必须通过 FileInfo ......),我仍然更喜欢它们作为静态方法,也许包装在 Utilities 静态类中,但是我知道这可能是有争议的......无论如何,对于提问者来说,很容易在扩展方法中转换上述代码;)
  • 此代码不可移植到其他平台,在 Unix 上您需要使用 Path.PathSeparator 而不是硬编码分号。
【解决方案2】:

这是有风险的,它不仅仅是在 PATH 中搜索目录。试试这个:

 Process.Start("wordpad.exe");

可执行文件存储在我机器上的 c:\Program Files\Windows NT\Accessories 中,该目录在路径上不是

HKCR\Applications 和 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths 键也在查找可执行文件中发挥作用。我相当确定周围还有其他类似的地雷,例如,64 位版本的 Windows 中的目录虚拟化可能会让你大吃一惊。

为了使这更可靠,我认为您需要 pinvoke AssocQueryString()。不确定,从来没有需要。更好的方法当然是不必问问题。

【讨论】:

  • 我要查询的应用程序将自己注册到路径 (mysqldump.exe)。如果没有,或者如果没有安装,我想禁用从 Windows 窗体应用程序使用 mysqlbackup 的选项。我只是不想硬编码文件的路径。
  • 如今,安装程序修改 PATH 的情况非常很少见。特别是对于实用程序,请先检查。我只会使用具有应用程序范围的设置,并在此处默认为“”。
  • 这是最近 Raymond Chen 帖子的主题。很难打败他的博客技巧,除了我是第一个。欣赏:blogs.msdn.com/b/oldnewthing/archive/2011/07/25/10189298.aspx
  • 更新了 Raymond Chen 帖子的链接:devblogs.microsoft.com/oldnewthing/20110725-00/?p=10073
【解决方案3】:

好吧,我想一个更好的方法......

这使用 where 命令,该命令至少在 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");
    }
}

【讨论】:

    【解决方案4】:

    我试用了 Dunc 的 where 进程,它可以工作,但它速度慢且占用大量资源,并且存在孤立进程的轻微危险。

    我喜欢 Eugene Mala 关于PathFindOnPath 的提示,因此我将其充实为一个完整的答案。这就是我在我们的自定义内部工具中使用的。

    /// <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;
    

    【讨论】:

      【解决方案5】:

      已接受的答案表明没有任何内置功能,但事实并非如此。 有一个标准的 WinAPI PathFindOnPath 用于执行此操作,它从 Windows 2000 开始可用。

      【讨论】:

        【解决方案6】:

        我也在追求同样的事情,我认为我现在最好的选择是使用对 CreateProcess 的本地调用来创建一个暂停的进程并等待成功;之后立即终止该过程。终止暂停的进程不应导致任何资源消耗[需要引用:)]

        我可能无法弄清楚实际使用的路径,但对于像 ExistsOnPath() 这样的简单要求,它应该这样做 - 直到有更好的解决方案。

        【讨论】:

        • 即使您创建挂起的进程,也可能会执行某些代码部分。如果你想测试系统路径中是否存在恶意软件或病毒——这种方法非常危险!
        【解决方案7】:

        更短更直接,这是发帖人想要的。

        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);
        

        【讨论】:

        • 您能否更新此答案以提供有关您在做什么以及为什么它与接受的不同的额外解释?这始终是一种最佳做法,但在用已确定的答案回答老问题时,它尤其很重要。通常,随着库和功能的变化,有充分的理由提出新的答案——当一个问题已经存在十年时,这当然是正确的!——但没有任何解释,社区不会理解为什么你的答案可能比现有答案更可取.感谢您的考虑。
        • 允许只回答代码,但也鼓励解释答案。考虑添加一些解释。
        • 谢谢,但问题有 c# 和 .net 标签。
        • 你仍然可以在 C# 中做同样的事情,对我来说似乎还不错。我想,我会使用这种方法,因为它不依赖于环境变量的字符串拆分。
        【解决方案8】:

        我结合@Ron 和@Hans Passant 的答案创建了一个类,该类通过调用PathFindOnPathApp Path 注册表项和PATH 中检查文件路径。它还允许省略文件扩展名。在这种情况下,它会从PATHEXT 探测几个可能的“可执行”文件扩展名。

        使用方法:

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

        代码如下:

        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);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多