如何获取同一PID的CDViewer.exe全部打开窗口标题?
解决同一PID下多窗口标题获取问题(以CDViewer.exe为例)
问题根源
你遇到的痛点非常典型:不管是C#里的Process.MainWindowTitle还是PowerShell中的mainWindowTitle属性,都只会返回进程中被标记为“主窗口”的第一个窗口标题。而像Citrix的CDViewer.exe这类进程,会在同一个PID下创建多个顶级窗口,但这些窗口不会被Process类识别为“主窗口”,所以只能抓到第一个。
要获取所有窗口标题,必须绕过Process类的限制,直接调用Windows原生API枚举系统中所有窗口,再筛选出属于目标PID的那些。
C#解决方案
我们需要借助user32.dll里的几个核心API来实现:
EnumWindows:枚举系统中所有顶级窗口GetWindowThreadProcessId:获取窗口所属的进程IDGetWindowText:读取窗口的标题文本IsWindowVisible:过滤掉后台隐藏的窗口(可选)
下面是完整的可运行代码:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; class CitrixWindowLister { // 导入Windows原生API [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); // 定义枚举窗口的委托类型 private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); // 存储窗口信息的实体类 public class WindowDetail { public IntPtr WindowHandle { get; set; } public string Title { get; set; } public int ProcessId { get; set; } } // 核心方法:获取指定PID的所有可见窗口 public static List<WindowDetail> GetAllWindowsForProcess(int targetPid) { List<WindowDetail> matchedWindows = new List<WindowDetail>(); EnumWindows((hWnd, lParam) => { // 跳过不可见窗口 if (!IsWindowVisible(hWnd)) return true; // 检查窗口所属PID是否匹配 GetWindowThreadProcessId(hWnd, out uint windowPid); if (windowPid != targetPid) return true; // 读取窗口标题 System.Text.StringBuilder titleBuilder = new System.Text.StringBuilder(256); GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity); string windowTitle = titleBuilder.ToString().Trim(); // 只保留有标题的窗口 if (!string.IsNullOrEmpty(windowTitle)) { matchedWindows.Add(new WindowDetail { WindowHandle = hWnd, Title = windowTitle, ProcessId = (int)windowPid }); } return true; }, IntPtr.Zero); return matchedWindows; } static void Main(string[] args) { string targetProcessName = "CDViewer"; Process[] citrixProcesses = Process.GetProcessesByName(targetProcessName); foreach (Process proc in citrixProcesses) { Console.WriteLine($"=== PID: {proc.Id} | 进程名: {proc.ProcessName} ==="); Console.WriteLine("已找到的窗口标题:"); List<WindowDetail> windows = GetAllWindowsForProcess(proc.Id); foreach (var window in windows) { Console.WriteLine($" - {window.Title}"); } Console.WriteLine("----------------------------------------"); } Console.ReadKey(); } }
代码说明
- 通过
EnumWindows遍历所有顶级窗口,用IsWindowVisible跳过后台隐藏窗口(如果需要包含隐藏窗口,可移除这步判断)。 - 对每个窗口,用
GetWindowThreadProcessId验证所属PID是否匹配目标进程。 - 用
GetWindowText读取标题,过滤掉空标题的窗口,最后收集所有符合条件的窗口信息并输出。
PowerShell解决方案
PowerShell可以通过Add-Type嵌入C#代码来复用上面的逻辑,实现更简洁的窗口枚举:
function Get-ProcessAllWindows { param( [Parameter(Mandatory=$true)] [int]$ProcessId ) # 嵌入C#代码实现API调用 Add-Type @" using System; using System.Collections.Generic; using System.Runtime.InteropServices; public class WindowEnumerator { [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd); public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); public static List<string> GetWindowTitles(int pid) { List<string> titles = new List<string>(); EnumWindows((hWnd, lParam) => { if (!IsWindowVisible(hWnd)) return true; GetWindowThreadProcessId(hWnd, out uint windowPid); if (windowPid != pid) return true; StringBuilder sb = new StringBuilder(256); GetWindowText(hWnd, sb, sb.Capacity); string title = sb.ToString().Trim(); if (!string.IsNullOrEmpty(title)) titles.Add(title); return true; }, IntPtr.Zero); return titles; } } "@ # 调用嵌入的C#方法获取窗口标题 [WindowEnumerator]::GetWindowTitles($ProcessId) } # 执行示例:获取CDViewer.exe的所有窗口 $citrixProcesses = Get-Process -Name CDViewer -ErrorAction SilentlyContinue if ($citrixProcesses) { foreach ($proc in $citrixProcesses) { Write-Host "PID: $($proc.Id) | 进程名: $($proc.ProcessName)" -ForegroundColor Cyan Write-Host "窗口列表:" Get-ProcessAllWindows -ProcessId $proc.Id | ForEach-Object { Write-Host " → $_" } Write-Host "------------------------" } } else { Write-Host "未找到CDViewer.exe进程" -ForegroundColor Red }
使用方法
直接运行这段脚本,它会自动定位所有CDViewer.exe进程,并输出每个PID对应的所有可见窗口标题。
关键注意事项
- 权限问题:如果目标进程是高权限运行的,需要以管理员身份启动你的C#程序或PowerShell脚本,否则可能无法获取部分窗口信息。
- 隐藏窗口:如果需要包括后台隐藏窗口,只需移除代码中
IsWindowVisible的判断逻辑。 - 空标题窗口:代码中默认过滤了空标题窗口,若需要保留,可去掉标题非空的判断。
内容的提问来源于stack exchange,提问作者Mirco Mares
相关产品推荐
相关产品推荐

