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
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> appPaths = new Lazy>(LoadAppPaths);
private static Lazy executableExtensions = new Lazy(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 LoadAppPaths()
{
var appPaths = new Dictionary(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);
}