diff --git a/src/WinDroid.Adb/Models/AdbPathResult.cs b/src/WinDroid.Adb/Models/AdbPathResult.cs
new file mode 100644
index 0000000..f20cfe7
--- /dev/null
+++ b/src/WinDroid.Adb/Models/AdbPathResult.cs
@@ -0,0 +1,71 @@
+namespace WinDroid.Adb.Models;
+
+///
+/// Represents the outcome of attempting to locate an ADB executable.
+///
+///
+/// A successful result carries a non-empty and a
+/// . A failed result carries a
+/// and a non-empty
+/// . Use and
+/// to construct values that honour these invariants.
+///
+public sealed class AdbPathResult
+{
+ ///
+ /// Gets a value indicating whether an ADB executable was located.
+ ///
+ public bool Found { get; init; }
+
+ ///
+ /// Gets the resolved absolute path to the ADB executable when
+ /// is ; otherwise
+ /// .
+ ///
+ public string? Path { get; init; }
+
+ ///
+ /// Gets a concise, user-safe explanation of why resolution failed when
+ /// is ; otherwise
+ /// .
+ ///
+ public string? ErrorMessage { get; init; }
+
+ ///
+ /// Creates a successful result for the given resolved executable path.
+ ///
+ /// The resolved absolute path to the ADB executable.
+ ///
+ /// is , empty, or whitespace.
+ ///
+ public static AdbPathResult Success(string path)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(path);
+
+ return new AdbPathResult
+ {
+ Found = true,
+ Path = path,
+ ErrorMessage = null,
+ };
+ }
+
+ ///
+ /// Creates a failed result with the given user-safe error message.
+ ///
+ /// A concise explanation of the failure.
+ ///
+ /// is , empty, or whitespace.
+ ///
+ public static AdbPathResult Failure(string errorMessage)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(errorMessage);
+
+ return new AdbPathResult
+ {
+ Found = false,
+ Path = null,
+ ErrorMessage = errorMessage,
+ };
+ }
+}
diff --git a/src/WinDroid.Adb/Services/AdbPathResolver.cs b/src/WinDroid.Adb/Services/AdbPathResolver.cs
new file mode 100644
index 0000000..87cfa96
--- /dev/null
+++ b/src/WinDroid.Adb/Services/AdbPathResolver.cs
@@ -0,0 +1,173 @@
+using WinDroid.Adb.Models;
+
+namespace WinDroid.Adb.Services;
+
+///
+/// Locates an ADB executable by checking an optional custom path and then the
+/// directories listed in the system PATH environment variable.
+///
+///
+/// This type only determines whether an ADB executable exists on disk. It does
+/// not launch ADB and does not verify that a located file is a genuine ADB
+/// binary. The system PATH is read on every call so that environment
+/// changes during the application lifetime are honoured.
+///
+public sealed class AdbPathResolver
+{
+ // The active application target is Windows, where the executable is named
+ // "adb.exe". The plain "adb" name is also checked so the portable library
+ // behaves sensibly on non-Windows hosts. "adb.exe" is checked first so
+ // resolution is deterministic when both names exist in the same directory.
+ private static readonly string[] ExecutableNames = ["adb.exe", "adb"];
+
+ ///
+ /// Attempts to locate an ADB executable.
+ ///
+ ///
+ /// Optional path to an ADB executable, typically supplied from
+ /// AdbSettings.CustomAdbPath. , empty, or
+ /// whitespace values are ignored and resolution falls back to the system
+ /// PATH.
+ ///
+ ///
+ /// A successful containing the resolved path, or
+ /// a failed result whose explains
+ /// why ADB could not be found. A missing ADB is an expected outcome and is
+ /// reported through the result rather than by throwing.
+ ///
+ public AdbPathResult Resolve(string? customAdbPath = null)
+ {
+ string? normalizedCustomPath = NormalizeToken(customAdbPath);
+ bool customPathSupplied = normalizedCustomPath is not null;
+
+ if (customPathSupplied && File.Exists(normalizedCustomPath))
+ {
+ string? resolved = GetFullPathSafe(normalizedCustomPath!);
+ if (resolved is not null)
+ {
+ return AdbPathResult.Success(resolved);
+ }
+
+ // The file exists but its path could not be normalized to an
+ // absolute path, so fall through to the system PATH search.
+ }
+
+ string? pathMatch = SearchSystemPath();
+ if (pathMatch is not null)
+ {
+ return AdbPathResult.Success(pathMatch);
+ }
+
+ return AdbPathResult.Failure(BuildNotFoundMessage(customPathSupplied));
+ }
+
+ ///
+ /// Searches the directories listed in the system PATH for a known ADB
+ /// executable name, returning the first match as an absolute path.
+ ///
+ private static string? SearchSystemPath()
+ {
+ string? pathVariable = Environment.GetEnvironmentVariable("PATH");
+ if (string.IsNullOrEmpty(pathVariable))
+ {
+ return null;
+ }
+
+ var visitedDirectories = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ foreach (string rawEntry in pathVariable.Split(Path.PathSeparator))
+ {
+ string? directory = NormalizeToken(rawEntry);
+ if (directory is null || !visitedDirectories.Add(directory))
+ {
+ // Blank or duplicate entry; skip without touching the filesystem.
+ continue;
+ }
+
+ string? match = FindExecutableInDirectory(directory);
+ if (match is not null)
+ {
+ return match;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns the absolute path to the first known ADB executable found in the
+ /// given directory, or if none is present or the
+ /// entry cannot be inspected.
+ ///
+ private static string? FindExecutableInDirectory(string directory)
+ {
+ foreach (string executableName in ExecutableNames)
+ {
+ string candidate = Path.Combine(directory, executableName);
+
+ // File.Exists is exception-free and returns false for malformed or
+ // inaccessible paths, so unusable PATH entries are skipped safely.
+ if (File.Exists(candidate))
+ {
+ string? resolved = GetFullPathSafe(candidate);
+ if (resolved is not null)
+ {
+ return resolved;
+ }
+
+ // Exists but cannot be normalized to an absolute path; skip it.
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Trims surrounding whitespace and a single pair of matching surrounding
+ /// quotation marks, returning when nothing usable
+ /// remains.
+ ///
+ private static string? NormalizeToken(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return null;
+ }
+
+ string trimmed = value.Trim();
+
+ if (trimmed.Length >= 2 && trimmed[0] == '"' && trimmed[^1] == '"')
+ {
+ trimmed = trimmed[1..^1].Trim();
+ }
+
+ return string.IsNullOrEmpty(trimmed) ? null : trimmed;
+ }
+
+ ///
+ /// Produces an absolute, normalized path, returning
+ /// if the path cannot be normalized.
+ ///
+ private static string? GetFullPathSafe(string path)
+ {
+ try
+ {
+ return Path.GetFullPath(path);
+ }
+ catch (Exception ex) when (
+ ex is ArgumentException
+ or PathTooLongException
+ or NotSupportedException
+ or System.Security.SecurityException)
+ {
+ return null;
+ }
+ }
+
+ private static string BuildNotFoundMessage(bool customPathSupplied) =>
+ customPathSupplied
+ ? "ADB could not be found. The supplied custom path did not point to a " +
+ "valid ADB executable file, and no ADB executable was found through " +
+ "the system PATH."
+ : "ADB could not be found through the system PATH.";
+}