.net 检查Windows路径中是否存在可执行文件

oknwwptz  于 2023-06-25  发布在  .NET
关注(0)|答案(9)|浏览(133)

如果我用ShellExecute运行一个进程(或者在.net中用System.Diagnostics.Process.Start()),要启动的进程的文件名不需要是完整路径。
如果我想启动记事本,我可以使用

Process.Start("notepad.exe");

而不是

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

因为directory c:\windows\system32是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中是否有预定义的类来执行此任务?

kmpatx3s

kmpatx3s1#

我认为它没有内置的东西,但是你可以用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;
}
x6yk4ghg

x6yk4ghg2#

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

Process.Start("wordpad.exe");

可执行文件存储在我的机器上的c:\Program Files\Windows NT\Accessories中,该目录 * 不在 * 路径上。
HKCR\Applications和HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths键在查找可执行文件时也起作用。我相当肯定还有其他类似的地雷,例如,64位版本的Windows中的目录虚拟化可能会让你绊倒。
为了使它更可靠,我认为您需要pinvoke AssocQueryString()。不确定,从来没有必要。更好的方法当然是不必问这个问题。

qco9c6ql

qco9c6ql3#

好吧,我觉得更好的方法……
这将使用 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");
    }
}
8zzbczxx

8zzbczxx4#

我尝试了Dunc的where进程,它工作,但它很慢,资源很重,而且有一个孤立进程的轻微危险。
我喜欢尤金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://learn.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;
sqougxex

sqougxex5#

接受的答案是没有内置的东西,但这不是真的。有一个标准的WinAPI PathFindOnPath可以做到这一点,它是自Windows 2000以来可用的。

p4tfgftt

p4tfgftt6#

更短更直接,这正是海报想要的。

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

06odsfpq7#

我也在追求同样的事情,我认为我现在最好的选择是使用本机调用CreateProcess来创建一个进程挂起并观察成功;随后立即终止该过程。终止一个暂停的进程不应该导致任何资源流失[引文需要:)]
我可能无法计算出实际使用的路径,但对于ExistsOnPath()这样的简单需求,它应该可以--直到有更好的解决方案。

idfiyjo8

idfiyjo88#

我结合了@罗恩和@Hans Passant的答案,创建了一个类,通过调用PathFindOnPath来检查App 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);
}
nr7wwzry

nr7wwzry9#

为什么不尝试/ catch Process.Start()方法并处理catch中的任何问题?
唯一的问题可能是,当找不到所需的可执行文件时,Process.Start()将返回一个相当不特定的Win32Exception。所以像catch (FileNotFoundException ex)这样的东西是不可能的。
但是你可以使用Win32Exception.NativeErrorCode属性来解决这个问题,以进行进一步的分析:

try
{
    Process proc = new Process();
    proc.StartInfo.FileName = "...";
    proc.Start();
    proc.WaitForExit();
}
// check into Win32Exceptions and their error codes!
catch (Win32Exception winEx)  
{
    if (winEx.NativeErrorCode == 2 || winEx.NativeErrorCode == 3) {
        // 2 => "The system cannot find the FILE specified."
        // 3 => "The system cannot find the PATH specified."
        throw new Exception($"Executable not found in path");
    }
    else
    {
        // unknown Win32Exception, re-throw to show the raw error msg
        throw;
    }
}

有关Win32Exception.NativeErrorCode的列表,请参见https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d?redirectedfrom=MSDN

相关问题