C# 检查Windows路径中是否存在可执行文件

C# 检查Windows路径中是否存在可执行文件,c#,.net,file,C#,.net,File,如果我使用ShellExecute(或在.net中使用System.Diagnostics.process.Start())运行进程,则要启动的进程文件名不需要是完整路径 如果我想启动记事本,我可以使用 Process.Start("notepad.exe"); 而不是 Process.Start(@"c:\windows\system32\notepad.exe"); 因为direcotryc:\windows\system32是PATH环境变量的一部分 如何在不执行流程和不解析PATH变

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

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

Process.Start("notepad.exe");
而不是

Process.Start(@"c:\windows\system32\notepad.exe");
因为direcotry
c:\windows\system32
是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


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

我认为没有内置的,但您可以通过以下方式执行此任务:


这是有风险的,不仅仅是搜索路径中的目录。试试这个:

 Process.Start("wordpad.exe");
可执行文件存储在我的计算机上的c:\Program Files\Windows NT\Accessories中,该目录不在路径上

HKCR\Applications和HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Path键在查找可执行文件时也起作用。我敢肯定还有其他类似的地雷,例如64位版本的Windows中的目录虚拟化可能会让你陷入困境


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

我也在追求同样的东西,我认为我现在最好的选择是使用对CreateProcess的本机调用来创建一个挂起的进程,并观察是否成功;随后立即终止进程。终止挂起的进程不应导致任何资源流失[需要引用:)]


我可能无法找出实际使用的路径,但对于一个简单的需求,如ExistsOnPath(),它应该这样做-直到有更好的解决方案。

好的,我认为有一个更好的方法

这将使用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");
    }
}

公认的答案是没有内置的,但事实并非如此。
有一个标准的WinAPI来实现这一点,它是从Windows 2000开始提供的。

我试用了Dunc的
,其中
进程可以工作,但是它速度慢,资源多,并且有一个孤立进程的轻微危险

我喜欢尤金·马拉关于
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;
//
///获取给定可执行文件名的完整路径,就像用户已输入此文件一样
///可在shell中执行。例如,Windows PATH环境变量将
///接受检查。如果Windows找不到文件名,则返回null。
/// 
///如果成功,则返回完整路径,否则返回null。
公共静态字符串GetFullPathFromWindows(字符串名)
{
如果(exeName.Length>=最大路径)
抛出新ArgumentException($“可执行文件名{exeName}必须少于{MAX_PATH}个字符。”,
姓名(exeName));
StringBuilder sb=新的StringBuilder(exeName,最大路径);
返回路径findonpath(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)]
静态外部布尔路径findonPath([In,Out]StringBuilder pszFile,[In]string[]ppszOtherDirs);
//从MAPIWIN.h:
私有常量int MAX_PATH=260;

我将@Ron和@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);
}
内部静态类CommandLinePathResolver
{
私有常量int MAX_PATH=260;
私有静态惰性应用路径=新惰性(LoadAppPath);
私有静态惰性可执行扩展=新惰性(LoadExecutableExtensions);
公共静态字符串TryGetFullPathForCommand(字符串命令)
{
if(Path.HasExtension(命令))
返回TryGetFullPathForFileName(命令);
返回TryGetFullPathByProbingExtensions(命令);
}
私有静态字符串[]LoadExecutableExtensions()=>Environment.GetEnvironmentVariable(“PathText”).Split(“;”);
私有静态字典loadAppPath()
{
var apppath=新字典(StringComparer.OrdinalIgnoreCase);
使用var key=Registry.LocalMachine.OpenSubKey(@“Software\Microsoft\Windows\CurrentVersion\App path”);
foreach(key.GetSubKeyNames()中的var subkeyName)
{
使用var subkey=key.OpenSubKey(subkeyName);
Add(subkeyName,subkey.GetValue(string.Empty)?.ToString());
}
返回应用路径;
}
私有静态字符串TryGetFullPathByProbingExtensions(字符串命令)
{
foreach(executableExtensions.Value中的变量扩展)
{
var result=TryGetFullPathForFileName(命令+扩展名);
如果(结果!=null)
返回结果;
}
返回null;
}
私有静态字符串TryGetFullPathForFileName(字符串文件名)=>
TryGetFullPathFromPathEnvironmentVariable(文件名)??TryGetFullPathFromAppPath(文件名);
私有静态字符串tryGetFullPathFromAppPath(字符串文件名)=>
apppath.Value.TryGetValue(文件名,out-var路径)?路径:null;
私有静态字符串TryGetFullPathFromPathEnvironmentVariable(字符串文件名)
{
如果(fileName.Length>=最大路径)
抛出新的ArgumentException($“可执行文件名{fileName}”必须少于{MAX_PATH}个字符。”,nameof(fileName));
var某人=
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);
}
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);