diff --git a/FindExecutable/ComponentSource.json b/FindExecutable/ComponentSource.json
index 5e73300..aaba352 100644
--- a/FindExecutable/ComponentSource.json
+++ b/FindExecutable/ComponentSource.json
@@ -8,7 +8,7 @@
{
"type": "git",
"url": "https://www.github.com/sgrottel/FindExecutable",
- "hash": "07160e7362cb7797f962ff98a707d6064d719ef3",
+ "hash": "4470fc9cea00acf2bed380e4cefe62137acd0ef7",
"path": "FindExecutable"
}
]
diff --git a/FindExecutable/FindExecutable.cs b/FindExecutable/FindExecutable.cs
index 006d9f3..2c9660c 100644
--- a/FindExecutable/FindExecutable.cs
+++ b/FindExecutable/FindExecutable.cs
@@ -1,202 +1,237 @@
-//
-// FindExecutable.cs
-// Copyright, by SGrottel.de https://www.github.com/sgrottel/FindExecutable
-// Open Source under the `MIT license`
-//
-// Copyright(c) 2023-2024 Sebastian Grottel
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Runtime.Versioning;
-
-namespace FindExecutable
-{
-
- ///
- /// Utility class to help finding the full file system path from an executable name
- ///
- public static class FindExecutable
- {
-
- /// The name of the executable to find.
- /// If set to true, the current execution directory is included in the list of search paths
- /// If set to true, the application domain base directory is included in the list of search paths
- /// If set to something other then null, the paths iterated within are included in the list of search paths
- /// The full file system path to the executable file requested, or null if the file is not found.
- ///
- /// The function searches in all paths specified by the `PATH` environment variable.
- ///
- /// Depending on the platform the code is run on, `executable` might be case sensitive.
- ///
- /// When searching on Windows and the name does not specify a file name extension,
- /// `.exe` is assumed, if no executable file without file name extension is found.
- ///
- /// When searching on Linux and the n ame does contain a file name extension,
- /// the extension is removed, if no executable file is found with it.
- ///
- public static string? FullPath(
- string executable,
- bool includeCurrentDirectory = false,
- bool includeBaseDirectory = false,
- IEnumerable? additionalPaths = null)
- {
- IEnumerable paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty();
- if (includeCurrentDirectory)
- paths = paths.Concat(new string[] { Environment.CurrentDirectory });
- if (includeBaseDirectory)
- paths = paths.Concat(new string[] { AppDomain.CurrentDomain.BaseDirectory });
- if (additionalPaths != null)
- paths = paths.Concat(additionalPaths);
-
- while (!string.IsNullOrEmpty(executable))
- {
- foreach (string path in paths)
- {
- if (string.IsNullOrWhiteSpace(path)) continue;
- if (!Directory.Exists(path)) continue;
- string p = Path.GetFullPath(path);
- if (!Directory.Exists(p)) continue;
-
- string f = Path.Combine(p, executable);
- if (File.Exists(f))
- {
- if (IsExecutable(f))
- {
- return f;
- }
- }
- }
-
- // Not found in first go. Maybe try fallback
- if (OperatingSystem.IsWindows())
- {
- if (string.IsNullOrEmpty(Path.GetExtension(executable)))
- {
- executable = Path.ChangeExtension(executable, ".exe");
- }
- else break;
- }
- else
- {
- if (!string.IsNullOrEmpty(Path.GetExtension(executable)))
- {
- executable = Path.GetFileNameWithoutExtension(executable);
- }
- else break;
- }
- }
-
- return null;
- }
-
- #region Test if File is Executable
-
- #region Windows P/Invoke
- [DllImport("shell32.dll")]
- private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, IntPtr psfi, uint cbSizeFileInfo, uint uFlags);
- private const uint SHGFI_EXETYPE = 0x000002000;
- #endregion
-
- [SupportedOSPlatform("Windows")]
- private static bool IsExecutableOnWindow(string path)
- {
- try
- {
- // Ask the windows shell what kind of file `path` points to.
- IntPtr exeType = SHGetFileInfo(path, 128, IntPtr.Zero, 0, SHGFI_EXETYPE);
- long wparam = exeType.ToInt64();
- int loWord = (int)(wparam & 0xffff);
- int hiWord = (int)(wparam >> 16);
-
- if (wparam != 0)
- {
- if (hiWord == 0x0000 && loWord == 0x5a4d)
- {
- return true; // Dos
- }
- else if (hiWord == 0x0000 && loWord == 0x4550)
- {
- return true; // Console
- }
- else if ((hiWord != 0x0000) && (loWord == 0x454E || loWord == 0x4550 || loWord == 0x454C))
- {
- return true; // Windows
- }
- }
- return false; // Very likely not an executable
- }
- catch { }
-
- // it exists, but shell does not answer. So, in doubt, give it a try... might work.
- return true;
- }
-
- #region Linux P/Invoke
- [DllImport("libc", SetLastError = true)]
- private static extern int access(string pathname, int mode);
- // https://codebrowser.dev/glibc/glibc/posix/unistd.h.html#283
- private const int X_OK = 1;
- #endregion
-
- [UnsupportedOSPlatform("Windows")]
- private static bool IsExecutableOnLinux(string path)
- {
- try
- {
- // access with X_OK to test if the user of the current process
- // should be able to execute the specified file.
- if (access(path, X_OK) == 0)
- {
- return true;
- }
- }
- catch { }
- return false;
- }
-
- ///
- /// Tests if a file is (likely) executable
- ///
- /// The path to the file to test
- /// True if the file is executable, false otherwise.
- /// If `path` does not point to an existing file, the function returns false.
- public static bool IsExecutable(string path)
- {
- if (!File.Exists(path)) return false;
- if (OperatingSystem.IsWindows())
- {
- return IsExecutableOnWindow(path);
- }
- else
- {
- return IsExecutableOnLinux(path);
- }
- }
-
- #endregion
-
- }
-
-}
+//
+// FindExecutable.cs
+// Copyright, by SGrottel.de https://www.github.com/sgrottel/FindExecutable
+// Open Source under the `MIT license`
+//
+// Copyright(c) 2023-2025 Sebastian Grottel
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+// Version history:
+//
+// v1.0 -- 2025-05-19
+// v0.4 -- 2024-05-20
+// v0.1 -- 2023-11-26
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace FindExecutable
+{
+
+ ///
+ /// Utility class to help finding the full file system path from an executable name
+ ///
+ public static class FindExecutable
+ {
+
+ /// The name of the executable to find.
+ /// If set to true, the current execution directory is included in the list of search paths
+ /// If set to true, the application domain base directory is included in the list of search paths
+ /// If set to something other then null, the paths iterated within are included in the list of search paths
+ /// The full file system path to the executable file requested, or null if the file is not found.
+ ///
+ /// The function searches in all paths specified by the `PATH` environment variable.
+ ///
+ /// Depending on the platform the code is run on, `executable` might be case sensitive.
+ ///
+ /// When searching on Windows and the name does not specify a file name extension,
+ /// `.exe` is assumed, if no executable file without file name extension is found.
+ ///
+ /// When searching on Linux and the n ame does contain a file name extension,
+ /// the extension is removed, if no executable file is found with it.
+ ///
+ public static string? FullPath(
+ string executable,
+ bool includeCurrentDirectory = false,
+ bool includeBaseDirectory = false,
+ IEnumerable? additionalPaths = null)
+ {
+ IEnumerable paths = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty();
+ if (includeCurrentDirectory)
+ {
+ paths = paths.Concat(new string[] { Environment.CurrentDirectory });
+ }
+
+ if (includeBaseDirectory)
+ {
+ paths = paths.Concat(new string[] { AppDomain.CurrentDomain.BaseDirectory });
+ }
+
+ if (additionalPaths != null)
+ {
+ paths = paths.Concat(additionalPaths);
+ }
+
+ while (!string.IsNullOrEmpty(executable))
+ {
+ foreach (string path in paths)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ continue;
+ }
+
+ if (!Directory.Exists(path))
+ {
+ continue;
+ }
+
+ string p = Path.GetFullPath(path);
+ if (!Directory.Exists(p))
+ {
+ continue;
+ }
+
+ string f = Path.Combine(p, executable);
+ if (File.Exists(f))
+ {
+ if (IsExecutable(f))
+ {
+ return f;
+ }
+ }
+ }
+
+ // Not found in first go. Maybe try fallback
+ if (OperatingSystem.IsWindows())
+ {
+ if (string.IsNullOrEmpty(Path.GetExtension(executable)))
+ {
+ executable = Path.ChangeExtension(executable, ".exe");
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(Path.GetExtension(executable)))
+ {
+ executable = Path.GetFileNameWithoutExtension(executable);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ #region Test if File is Executable
+
+ #region Windows P/Invoke
+ [DllImport("shell32.dll")]
+ private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, IntPtr psfi, uint cbSizeFileInfo, uint uFlags);
+ private const uint SHGFI_EXETYPE = 0x000002000;
+ #endregion
+
+ [SupportedOSPlatform("Windows")]
+ private static bool IsExecutableOnWindow(string path)
+ {
+ try
+ {
+ // Ask the windows shell what kind of file `path` points to.
+ IntPtr exeType = SHGetFileInfo(path, 128, IntPtr.Zero, 0, SHGFI_EXETYPE);
+ long wparam = exeType.ToInt64();
+ int loWord = (int)(wparam & 0xffff);
+ int hiWord = (int)(wparam >> 16);
+
+ if (wparam != 0)
+ {
+ if (hiWord == 0x0000 && loWord == 0x5a4d)
+ {
+ return true; // Dos
+ }
+ else if (hiWord == 0x0000 && loWord == 0x4550)
+ {
+ return true; // Console
+ }
+ else if ((hiWord != 0x0000) && (loWord == 0x454E || loWord == 0x4550 || loWord == 0x454C))
+ {
+ return true; // Windows
+ }
+ }
+ return false; // Very likely not an executable
+ }
+ catch { }
+
+ // it exists, but shell does not answer. So, in doubt, give it a try... might work.
+ return true;
+ }
+
+ #region Linux P/Invoke
+ [DllImport("libc", SetLastError = true)]
+ private static extern int access(string pathname, int mode);
+ // https://codebrowser.dev/glibc/glibc/posix/unistd.h.html#283
+ private const int X_OK = 1;
+ #endregion
+
+ [UnsupportedOSPlatform("Windows")]
+ private static bool IsExecutableOnLinux(string path)
+ {
+ try
+ {
+ // access with X_OK to test if the user of the current process
+ // should be able to execute the specified file.
+ if (access(path, X_OK) == 0)
+ {
+ return true;
+ }
+ }
+ catch { }
+ return false;
+ }
+
+ ///
+ /// Tests if a file is (likely) executable
+ ///
+ /// The path to the file to test
+ /// True if the file is executable, false otherwise.
+ /// If `path` does not point to an existing file, the function returns false.
+ public static bool IsExecutable(string path)
+ {
+ if (!File.Exists(path))
+ {
+ return false;
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ return IsExecutableOnWindow(path);
+ }
+ else
+ {
+ return IsExecutableOnLinux(path);
+ }
+ }
+
+ #endregion
+
+ }
+
+}
diff --git a/LICENSE b/LICENSE
index 37ff024..560bf75 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Sebastian Grottel
+Copyright (c) 2023-2025 Sebastian Grottel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 72db123..5066a9c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# FindExecutable
+
C# code to find the full path of an executable file by searching the system's search paths.
It is similar to `locate` or `get-command`.
@@ -15,11 +16,16 @@ Nuget:
The code was previously part of [SGrottel's Tiny Tools Collection (https://github.com/sgrottel/tiny-tools-collection)](https://github.com/sgrottel/tiny-tools-collection).
+
## Code
+
The code is located in the `FindExecutable` subdirectiory.
+
### Example
+
`FindExecutable` is a static class with one public static method:
+
```cs
public static string? FullPath(
string executable,
@@ -37,7 +43,9 @@ The function will return the full file system path to the executable file reques
A description of how the function performs it's search can be found in the function comment in the source code.
+
### How to Add to Your Project
+
You can simple copy this directory and all it's files to your project.
Or, you can use the `SGrottel.FindExecutable.nuget` to import the files into your project.
@@ -45,16 +53,21 @@ This way, if you manage your dependencies with tooling, you can be automatically
[SGrottel.FindExecutable (https://www.nuget.org/packages/SGrottel.FindExecutable)](https://www.nuget.org/packages/SGrottel.FindExecutable)
+
## Contributions
+
Contributions to this project are welcome!
+
* Open [Issues](https://github.com/sgrottel/FindExecutable/issues) here on Github
* Create Pull Requests with Improvements (I recommend you talk to me first, e.g. via e-mail or issues)
* Reach out to me, e.g. via [the contact form on my website (https://www.sgrottel.de/about)](https://www.sgrottel.de/about).
+
## License
+
This project is freely available under the terms of the [MIT License](./LICENSE)
- Copyright (c) 2023-2024 Sebastian Grottel
+ Copyright (c) 2023-2025 Sebastian Grottel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/SGrottel.FindExecutable.nuspec b/SGrottel.FindExecutable.nuspec
index 562130e..9c68148 100644
--- a/SGrottel.FindExecutable.nuspec
+++ b/SGrottel.FindExecutable.nuspec
@@ -6,7 +6,7 @@
SGrottel.FindExecutable
- 0.4.0.5
+ 1.0.0
SGrottel.FindExecutable
SGrottel
SGrottel
@@ -14,7 +14,7 @@
https://github.com/sgrottel/FindExecutable
false
C# code to find the full path of an executable file by searching the system's search paths.
- Copyright 2023-2024
+ Copyright 2023-2025
FindExecutable
docs/README.md
diff --git a/package_readme.md b/package_readme.md
index 3e07f35..1729cb5 100644
--- a/package_readme.md
+++ b/package_readme.md
@@ -23,7 +23,7 @@ The function will return the full file system path to the executable file reques
## License
This project is freely available under the terms of the [MIT License](./LICENSE)
- Copyright (c) 2023-2024 Sebastian Grottel
+ Copyright (c) 2023-2025 Sebastian Grottel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal