| File: src\Shared\PathLookupHelper.cs | Web Access |
| Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; /// <summary> /// Provides helper methods for looking up executables on the system PATH. /// </summary> internal static class PathLookupHelper { /// <summary> /// Finds the full path of a command by searching the system PATH. /// On Windows, this also searches for executables with common extensions (.exe, .cmd, .bat, etc.). /// </summary> /// <param name="command">The command name to search for.</param> /// <returns>The full path to the executable if found; otherwise, <c>null</c>.</returns> public static string? FindFullPathFromPath(string command) { var pathExtensions = OperatingSystem.IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(';', StringSplitOptions.RemoveEmptyEntries) ?? [] : null; return FindFullPathFromPath(command, Environment.GetEnvironmentVariable("PATH"), Path.PathSeparator, File.Exists, pathExtensions); } /// <summary> /// Finds the full path of a command by searching the specified PATH variable. /// </summary> /// <param name="command">The command name to search for.</param> /// <param name="pathVariable">The PATH environment variable value to search.</param> /// <param name="pathSeparator">The character used to separate paths in the PATH variable.</param> /// <param name="fileExists">A function to check if a file exists at a given path.</param> /// <param name="pathExtensions">Optional array of executable extensions to try (e.g., .exe, .cmd). When provided, these extensions will be appended to the command if not already present.</param> /// <returns>The full path to the executable if found; otherwise, <c>null</c>.</returns> internal static string? FindFullPathFromPath(string command, string? pathVariable, char pathSeparator, Func<string, bool> fileExists, string[]? pathExtensions = null) { Debug.Assert(!string.IsNullOrWhiteSpace(command)); // If the command already has a known extension, just search for it directly. if (pathExtensions is not null && pathExtensions.Any(ext => command.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) { return FindFullPath(command, pathVariable, pathSeparator, fileExists, pathExtensions: null); } return FindFullPath(command, pathVariable, pathSeparator, fileExists, pathExtensions); } private static string? FindFullPath(string command, string? pathVariable, char pathSeparator, Func<string, bool> fileExists, string[]? pathExtensions) { foreach (var directory in (pathVariable ?? string.Empty).Split(pathSeparator, StringSplitOptions.RemoveEmptyEntries)) { // On Windows, search each directory completely with all PATHEXT extensions before moving to the next. // This matches Windows command lookup behavior where directory order takes precedence. if (pathExtensions is not null && pathExtensions.Length > 0) { foreach (var extension in pathExtensions) { var fullPathWithExt = Path.Combine(directory, command + extension); if (fileExists(fullPathWithExt)) { return fullPathWithExt; } } } // Try exact match (for non-Windows, or as fallback on Windows if no extension match found in this directory). var fullPath = Path.Combine(directory, command); if (fileExists(fullPath)) { return fullPath; } } return null; } }