| File: CommandPathResolver.cs | Web Access |
| Project: src\src\Aspire.Hosting.Tasks\Aspire.Hosting.Tasks.csproj (Aspire.Hosting.Tasks) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Hosting.Tasks; internal static class CommandPathResolver { public static string? ResolveFromPath(string command, string? path = null) { return EnumerateFromPath(command, path).FirstOrDefault(); } public static IEnumerable<string> EnumerateFromPath(string command, string? path = null) { path ??= Environment.GetEnvironmentVariable("PATH"); if (string.IsNullOrWhiteSpace(path)) { yield break; } var executableNames = GetExecutableNames(command); var seenPaths = new HashSet<string>(IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); // PATH entries can be quoted, for example: // C:\Tools;"C:\Program Files\dotnet";C:\Users\user\.dotnet\tools foreach (var pathEntry in path.Split(Path.PathSeparator)) { var directory = TryGetFullPath(pathEntry.Trim().Trim('"')); if (directory is null) { continue; } foreach (var executableName in executableNames) { var candidate = Path.Combine(directory, executableName); if (seenPaths.Add(candidate) && FileExistsAndIsExecutable(candidate)) { yield return candidate; } } } } private static string? TryGetFullPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return null; } try { return Path.GetFullPath(path); } catch (Exception ex) when (ex is ArgumentException or NotSupportedException or PathTooLongException or System.Security.SecurityException) { return null; } } private static string[] GetExecutableNames(string command) { return IsWindows() ? [$"{command}.exe", $"{command}.cmd", $"{command}.bat", command] : [command]; } private static bool FileExistsAndIsExecutable(string path) { if (!File.Exists(path)) { return false; } #if NET if (OperatingSystem.IsWindows()) { return true; } try { const UnixFileMode ExecuteBits = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; return (File.GetUnixFileMode(path) & ExecuteBits) != 0; } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } #else // .NET Framework only runs on Windows, where file existence is sufficient. return true; #endif } private static bool IsWindows() => Path.DirectorySeparatorChar == '\\'; }