using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using WhiteMagic.Native; using WhiteMagic.Windows; namespace WhiteMagic.ProcessDiscovery; /// /// Discovers running processes by name, window title, or window handle so they can be /// attached through a session. /// public static class ApplicationFinder { /// /// Enumerates processes whose image name matches /// (extension optional). /// public static IEnumerable Enumerate(string processName) { ArgumentException.ThrowIfNullOrEmpty(processName); return Process.GetProcessesByName(GetNameWithoutExtension(processName)); } /// /// Returns the unique process whose image name matches . /// /// Zero or multiple processes match. public static Process OpenProcess(string processName) { Process[] candidates = Enumerate(processName).ToArray(); if (candidates.Length == 0) { throw new InvalidOperationException( $"No process named '{processName}' was found."); } if (candidates.Length > 1) { string list = string.Join(", ", candidates.Select(p => $"{p.ProcessName}:{p.Id}")); foreach (Process candidate in candidates) candidate.Dispose(); throw new InvalidOperationException( $"Process name '{processName}' is ambiguous ({candidates.Length} matches): {list}"); } Process result = candidates[0]; for (int i = 1; i < candidates.Length; i++) candidates[i].Dispose(); return result; } /// /// Enumerates processes that own a top-level window whose title equals /// . /// public static IEnumerable FindByWindowTitle(string title) { ArgumentException.ThrowIfNullOrEmpty(title); var seen = new HashSet(); foreach (RemoteWindow window in WindowFactory.GetWindows()) { if (!string.Equals(window.Text, title, StringComparison.Ordinal)) continue; uint pid = window.ProcessId; if (pid == 0 || !seen.Add((int)pid)) continue; Process? process; try { process = global::System.Diagnostics.Process.GetProcessById((int)pid); } catch { continue; } yield return process; } } /// /// Returns the unique process that owns a top-level window titled . /// /// Zero or multiple windows match. public static Process OpenByWindowTitle(string title) { Process[] candidates = FindByWindowTitle(title).ToArray(); if (candidates.Length == 0) { throw new InvalidOperationException( $"No top-level window titled '{title}' was found."); } if (candidates.Length > 1) { throw new InvalidOperationException( $"Window title '{title}' is ambiguous ({candidates.Length} matches): " + string.Join(", ", candidates.Select(p => $"{p.ProcessName}:{p.Id}"))); } return candidates[0]; } /// /// Returns the process that owns the specified window handle. /// public static Process OpenByWindowHandle(IntPtr handle) { if (handle == IntPtr.Zero) throw new ArgumentException("Window handle cannot be zero.", nameof(handle)); uint tid = NativeMethods.GetWindowThreadProcessId(handle, out uint processId); if (tid == 0 || processId == 0) { int error = Marshal.GetLastPInvokeError(); throw new InvalidOperationException( $"GetWindowThreadProcessId failed for handle {handle:X}: error {error}."); } try { return global::System.Diagnostics.Process.GetProcessById((int)processId); } catch (ArgumentException) { throw new InvalidOperationException( $"Process {processId} owning window {handle:X} is no longer running."); } } private static string GetNameWithoutExtension(string name) { if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) return name[..^4]; return name; } }