2026-02-26 16:55:40 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
|
|
|
|
|
打印PC微信通讯录的所有子控件
|
|
|
|
|
|
需要以管理员权限运行Python
|
|
|
|
|
|
"""
|
2026-03-03 11:05:33 +08:00
|
|
|
|
import os
|
2026-02-26 16:55:40 +08:00
|
|
|
|
import uiautomation as auto
|
|
|
|
|
|
import time
|
|
|
|
|
|
import ctypes
|
|
|
|
|
|
from ctypes import wintypes, POINTER, byref, c_int, c_ulong, c_void_p
|
|
|
|
|
|
import comtypes.client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_control_tree(control, depth=0, max_depth=20, file=None):
|
|
|
|
|
|
"""递归打印控件树"""
|
|
|
|
|
|
if depth > max_depth:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取控件信息
|
|
|
|
|
|
control_type = control.ControlTypeName
|
|
|
|
|
|
class_name = control.ClassName or ""
|
|
|
|
|
|
name = control.Name or ""
|
|
|
|
|
|
automation_id = control.AutomationId or ""
|
|
|
|
|
|
rect = control.BoundingRectangle
|
|
|
|
|
|
|
|
|
|
|
|
# 缩进
|
|
|
|
|
|
indent = " " * depth
|
|
|
|
|
|
|
|
|
|
|
|
# 打印控件信息
|
|
|
|
|
|
line = f"{indent}ControlType: {control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect} Depth: {depth}"
|
|
|
|
|
|
print(line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
# 递归打印子控件
|
|
|
|
|
|
try:
|
|
|
|
|
|
children = control.GetChildren()
|
|
|
|
|
|
for child in children:
|
|
|
|
|
|
print_control_tree(child, depth + 1, max_depth, file)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
error_line = f"{indent} [Error getting children: {e}]"
|
|
|
|
|
|
print(error_line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(error_line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_control_tree_raw_comtypes(uiAutomation, element, depth=0, max_depth=20, file=None):
|
|
|
|
|
|
"""使用comtypes直接访问IUIAutomation接口"""
|
|
|
|
|
|
if depth > max_depth:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取控件属性
|
|
|
|
|
|
name = element.CurrentName or ""
|
|
|
|
|
|
class_name = element.CurrentClassName or ""
|
|
|
|
|
|
automation_id = element.CurrentAutomationId or ""
|
|
|
|
|
|
control_type_id = element.CurrentControlType
|
|
|
|
|
|
localized_control_type = element.CurrentLocalizedControlType or ""
|
|
|
|
|
|
|
|
|
|
|
|
# 获取BoundingRectangle
|
|
|
|
|
|
try:
|
|
|
|
|
|
rect = element.CurrentBoundingRectangle
|
|
|
|
|
|
rect_str = f"({rect.left},{rect.top},{rect.right},{rect.bottom})"
|
|
|
|
|
|
except:
|
|
|
|
|
|
rect_str = "N/A"
|
|
|
|
|
|
|
|
|
|
|
|
indent = " " * depth
|
|
|
|
|
|
line = f"{indent}ControlType: {localized_control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect_str} Depth: {depth}"
|
|
|
|
|
|
print(line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
# 使用RawViewWalker遍历子元素
|
|
|
|
|
|
tree_walker = uiAutomation.RawViewWalker
|
|
|
|
|
|
child = tree_walker.GetFirstChildElement(element)
|
|
|
|
|
|
|
|
|
|
|
|
while child:
|
|
|
|
|
|
print_control_tree_raw_comtypes(uiAutomation, child, depth + 1, max_depth, file)
|
|
|
|
|
|
# 获取下一个兄弟元素
|
|
|
|
|
|
child = tree_walker.GetNextSiblingElement(child)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
indent = " " * depth
|
|
|
|
|
|
error_line = f"{indent}[Error: {e}]"
|
|
|
|
|
|
print(error_line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(error_line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def enum_windows_callback(hwnd, results):
|
|
|
|
|
|
"""枚举窗口回调函数"""
|
|
|
|
|
|
import win32gui
|
|
|
|
|
|
import win32process
|
|
|
|
|
|
|
|
|
|
|
|
if win32gui.IsWindowVisible(hwnd):
|
|
|
|
|
|
class_name = win32gui.GetClassName(hwnd)
|
|
|
|
|
|
title = win32gui.GetWindowText(hwnd)
|
|
|
|
|
|
_, pid = win32process.GetWindowThreadProcessId(hwnd)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否是微信相关窗口
|
|
|
|
|
|
try:
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
result = subprocess.run(['tasklist', '/FI', f'PID eq {pid}', '/NH'],
|
|
|
|
|
|
capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
|
|
|
|
|
if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower():
|
|
|
|
|
|
results.append((hwnd, class_name, title, pid))
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_win32_children(hwnd, depth=0, max_depth=20, file=None):
|
|
|
|
|
|
"""使用Win32 API枚举子窗口"""
|
|
|
|
|
|
import win32gui
|
|
|
|
|
|
|
|
|
|
|
|
if depth > max_depth:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
class_name = win32gui.GetClassName(hwnd)
|
|
|
|
|
|
title = win32gui.GetWindowText(hwnd)
|
|
|
|
|
|
rect = win32gui.GetWindowRect(hwnd)
|
|
|
|
|
|
|
|
|
|
|
|
indent = " " * depth
|
|
|
|
|
|
line = f"{indent}HWND: 0x{hwnd:X} ClassName: {class_name} Title: {title} Rect: {rect} Depth: {depth}"
|
|
|
|
|
|
print(line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
# 枚举子窗口
|
|
|
|
|
|
def child_callback(child_hwnd, _):
|
|
|
|
|
|
print_win32_children(child_hwnd, depth + 1, max_depth, file)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
win32gui.EnumChildWindows(hwnd, child_callback, None)
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_accessible_tree(hwnd, depth=0, max_depth=20, file=None):
|
|
|
|
|
|
"""使用IAccessible接口枚举控件"""
|
|
|
|
|
|
import ctypes
|
|
|
|
|
|
from ctypes import POINTER, byref
|
|
|
|
|
|
from ctypes import windll, oledll
|
|
|
|
|
|
|
|
|
|
|
|
if depth > max_depth:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
import comtypes.client
|
|
|
|
|
|
from comtypes import IUnknown
|
|
|
|
|
|
|
|
|
|
|
|
# 获取IAccessible接口
|
|
|
|
|
|
accessible = oledll.oleacc.AccessibleObjectFromWindow(
|
|
|
|
|
|
hwnd,
|
|
|
|
|
|
0xFFFFFFFC, # OBJID_CLIENT
|
|
|
|
|
|
comtypes.IUnknown._iid_,
|
|
|
|
|
|
byref(ctypes.POINTER(comtypes.IUnknown)())
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试获取更多信息
|
|
|
|
|
|
indent = " " * depth
|
|
|
|
|
|
line = f"{indent}HWND: 0x{hwnd:X} [Accessible Object Available]"
|
|
|
|
|
|
print(line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
indent = " " * depth
|
|
|
|
|
|
line = f"{indent}HWND: 0x{hwnd:X} [No IAccessible: {e}]"
|
|
|
|
|
|
print(line)
|
|
|
|
|
|
if file:
|
|
|
|
|
|
file.write(line + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print("打印PC微信通讯录的所有子控件")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
# 设置全局搜索超时时间
|
|
|
|
|
|
auto.SetGlobalSearchTimeout(10)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取桌面根控件
|
|
|
|
|
|
root = auto.GetRootControl()
|
|
|
|
|
|
print("桌面根控件:", root)
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
# 查找微信主窗口 - 尝试多种方式
|
|
|
|
|
|
print("正在查找微信窗口...")
|
|
|
|
|
|
|
|
|
|
|
|
wechat_window = None
|
|
|
|
|
|
|
|
|
|
|
|
# 方式1: 通过ClassName查找 (旧版微信)
|
|
|
|
|
|
wechat_window = auto.WindowControl(searchDepth=1, ClassName='WeChatMainWndForPC')
|
|
|
|
|
|
if wechat_window.Exists(2, 1):
|
|
|
|
|
|
print(f"方式1找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 方式2: 通过Name查找
|
|
|
|
|
|
wechat_window = auto.WindowControl(searchDepth=1, Name='微信')
|
|
|
|
|
|
if wechat_window.Exists(2, 1):
|
|
|
|
|
|
print(f"方式2找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 方式3: 通过进程名查找 (新版微信 WeChatAppEx.exe)
|
|
|
|
|
|
print("尝试通过进程查找微信窗口...")
|
|
|
|
|
|
for window in root.GetChildren():
|
|
|
|
|
|
try:
|
|
|
|
|
|
process_id = window.ProcessId
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
result = subprocess.run(['tasklist', '/FI', f'PID eq {process_id}', '/NH'],
|
|
|
|
|
|
capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
|
|
|
|
|
if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower():
|
|
|
|
|
|
wechat_window = window
|
|
|
|
|
|
print(f"方式3找到微信窗口: {window.Name}, ClassName: {window.ClassName}, PID: {process_id}")
|
|
|
|
|
|
break
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if wechat_window is None or not wechat_window.Exists(1, 1):
|
|
|
|
|
|
print("\n未找到微信窗口!请确保:")
|
|
|
|
|
|
print("1. 微信已打开并登录")
|
|
|
|
|
|
print("2. 以管理员权限运行此脚本")
|
|
|
|
|
|
print("\n尝试打印所有顶级窗口以帮助调试...")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
for window in root.GetChildren():
|
|
|
|
|
|
try:
|
|
|
|
|
|
print(f"Window: Name='{window.Name}' ClassName='{window.ClassName}' PID={window.ProcessId}")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
# 打印微信窗口的基本信息
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print("微信主窗口信息:")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print(f"Name: {wechat_window.Name}")
|
|
|
|
|
|
print(f"ClassName: {wechat_window.ClassName}")
|
|
|
|
|
|
print(f"AutomationId: {wechat_window.AutomationId}")
|
|
|
|
|
|
print(f"ControlType: {wechat_window.ControlTypeName}")
|
|
|
|
|
|
print(f"ProcessId: {wechat_window.ProcessId}")
|
|
|
|
|
|
print(f"Handle: {wechat_window.NativeWindowHandle}")
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
# 输出文件
|
2026-03-03 11:05:33 +08:00
|
|
|
|
output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_output.txt')
|
2026-02-26 16:55:40 +08:00
|
|
|
|
|
|
|
|
|
|
# 首先尝试标准方法
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print("方式1: 使用标准UIAutomation遍历控件...")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
|
|
|
|
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
f.write("=" * 80 + "\n")
|
|
|
|
|
|
f.write("微信窗口控件树 - 标准UIAutomation\n")
|
|
|
|
|
|
f.write("=" * 80 + "\n\n")
|
|
|
|
|
|
f.write(f"微信窗口: Name={wechat_window.Name}, ClassName={wechat_window.ClassName}\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
print_control_tree(wechat_window, depth=0, max_depth=20, file=f)
|
|
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
print(f"标准方法输出已保存到: {output_file}")
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试使用RawViewWalker (可以获取更多控件)
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print("方式2: 使用RawViewWalker遍历控件 (包括原始控件)...")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
|
2026-03-03 11:05:33 +08:00
|
|
|
|
output_file_raw = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_raw.txt')
|
2026-02-26 16:55:40 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 加载UIAutomation类型库
|
|
|
|
|
|
comtypes.client.GetModule('UIAutomationCore.dll')
|
|
|
|
|
|
from comtypes.gen.UIAutomationClient import IUIAutomation, CUIAutomation
|
|
|
|
|
|
|
|
|
|
|
|
# 创建IUIAutomation实例
|
|
|
|
|
|
uiAutomation = comtypes.CoCreateInstance(CUIAutomation._reg_clsid_, interface=IUIAutomation)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取RootElement
|
|
|
|
|
|
root_element = uiAutomation.GetRootElement()
|
|
|
|
|
|
|
|
|
|
|
|
# 找到微信窗口元素
|
|
|
|
|
|
condition = uiAutomation.CreatePropertyCondition(30005, "微信") # UIA_NamePropertyId = 30005
|
|
|
|
|
|
|
|
|
|
|
|
# 查找微信窗口 (TreeScope_Children = 2)
|
|
|
|
|
|
wechat_element = root_element.FindFirst(2, condition)
|
|
|
|
|
|
|
|
|
|
|
|
if wechat_element:
|
|
|
|
|
|
with open(output_file_raw, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
f.write("=" * 80 + "\n")
|
|
|
|
|
|
f.write("微信窗口控件树 - RawViewWalker\n")
|
|
|
|
|
|
f.write("=" * 80 + "\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
print_control_tree_raw_comtypes(uiAutomation, wechat_element, depth=0, max_depth=20, file=f)
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\nRawViewWalker输出已保存到: {output_file_raw}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("使用RawViewWalker未找到微信窗口元素")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"RawViewWalker方法出错: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试使用Win32 API枚举窗口
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print("方式3: 使用Win32 API枚举所有子窗口...")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
import win32gui
|
|
|
|
|
|
import win32process
|
|
|
|
|
|
|
|
|
|
|
|
hwnd = wechat_window.NativeWindowHandle
|
|
|
|
|
|
|
2026-03-03 11:05:33 +08:00
|
|
|
|
output_file_win32 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_win32.txt')
|
2026-02-26 16:55:40 +08:00
|
|
|
|
|
|
|
|
|
|
with open(output_file_win32, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
f.write("=" * 80 + "\n")
|
|
|
|
|
|
f.write("微信窗口控件树 - Win32 API\n")
|
|
|
|
|
|
f.write("=" * 80 + "\n\n")
|
|
|
|
|
|
|
|
|
|
|
|
print_win32_children(hwnd, depth=0, max_depth=20, file=f)
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\nWin32 API输出已保存到: {output_file_win32}")
|
|
|
|
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
print("未安装pywin32,跳过Win32 API方式")
|
|
|
|
|
|
print("可以通过 'pip install pywin32' 安装")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Win32 API方法出错: {e}")
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print("完成!")
|
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
|
print()
|
|
|
|
|
|
print("说明:")
|
|
|
|
|
|
print("新版微信(WeChatAppEx.exe)使用CEF/Chromium渲染UI,")
|
|
|
|
|
|
print("其内部控件不通过Windows原生控件实现,")
|
|
|
|
|
|
print("因此UIAutomation和Win32 API都无法直接访问其内部控件。")
|
|
|
|
|
|
print()
|
|
|
|
|
|
print("如需获取微信控件信息,可以尝试:")
|
|
|
|
|
|
print("1. 使用Microsoft Accessibility Insights工具")
|
|
|
|
|
|
print("2. 使用Chrome DevTools Protocol (如果微信支持)")
|
|
|
|
|
|
print("3. 使用OCR或图像识别技术")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
main()
|