91 lines
3.3 KiB
C#
91 lines
3.3 KiB
C#
using System.Management;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using static EonaCat.RdpColorizer.NativeMethods;
|
|
|
|
namespace EonaCat.RdpColorizer
|
|
{
|
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
|
|
|
/// <summary>
|
|
/// Figures out which server/IP an mstsc.exe window is actually connected
|
|
/// to, so the watcher can match it against saved profiles. Prefers the
|
|
/// process command line (exact, e.g. "10.0.0.5:3389") and falls back to
|
|
/// the window title (e.g. "SERVER01 - Remote Desktop Connection") when
|
|
/// the command line can't be read - which happens if mstsc.exe is
|
|
/// running elevated or under a different user account than this app.
|
|
/// </summary>
|
|
internal static class RdpTargetResolver
|
|
{
|
|
public static string? Resolve(nint hwnd)
|
|
{
|
|
GetWindowThreadProcessId(hwnd, out int pid);
|
|
|
|
return TryGetCommandLineTarget(pid) ?? TryGetTitleTarget(hwnd);
|
|
}
|
|
|
|
private static string? TryGetCommandLineTarget(int pid)
|
|
{
|
|
try
|
|
{
|
|
using var searcher = new ManagementObjectSearcher(
|
|
$"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {pid}");
|
|
|
|
foreach (ManagementObject mo in searcher.Get())
|
|
{
|
|
string? cmdLine = mo["CommandLine"]?.ToString();
|
|
if (string.IsNullOrEmpty(cmdLine))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Match match = Regex.Match(cmdLine, @"/v:([^\s""]+)", RegexOptions.IgnoreCase);
|
|
if (match.Success)
|
|
{
|
|
return Normalize(match.Groups[1].Value);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// WMI query can fail for permission reasons (e.g. mstsc.exe
|
|
// running elevated); fall back to reading the window title.
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static string? TryGetTitleTarget(nint hwnd)
|
|
{
|
|
var sb = new StringBuilder(512);
|
|
GetWindowText(hwnd, sb, sb.Capacity);
|
|
string title = sb.ToString();
|
|
|
|
// Typical titles: "SERVER01 - Remote Desktop Connection" or,
|
|
// while still negotiating, just "Remote Desktop Connection".
|
|
Match match = Regex.Match(title, @"^(.*?)\s*-\s*Remote Desktop Connection", RegexOptions.IgnoreCase);
|
|
if (match.Success && !string.IsNullOrWhiteSpace(match.Groups[1].Value))
|
|
{
|
|
return Normalize(match.Groups[1].Value);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string Normalize(string target)
|
|
{
|
|
target = target.Trim();
|
|
|
|
// Drop a trailing ":port" (but leave bare IPv6 addresses alone -
|
|
// we only strip when there's exactly one colon, i.e. IPv4/hostname:port).
|
|
int colonCount = target.Split(':').Length - 1;
|
|
if (colonCount == 1)
|
|
{
|
|
int idx = target.IndexOf(':');
|
|
target = target[..idx];
|
|
}
|
|
return target;
|
|
}
|
|
}
|
|
}
|