背景:
我工作中经常运行一个windows程序,获取并使用该程序的计算结果,但是该程序又不能满足我们的自动化要求,又无法破解其算法,怎么办?那就用Python做个批处理任务吧。关键点是如何运行外部exe、获取句柄及一些操作。
关键步骤:
1. 启动和关闭应用程序,我采用非阻塞式启动应用程序。
2. 遍历窗体。
3. 触发按钮。
4. 获取文本框内容。
环境:
1.程序使用到了Spy++ 工具。
# _*_ encoding:utf-8 _*_
import time
import logging
import win32gui
import win32con
import os
import subprocess
import psutil
from timeloop import Timeloop
from datetime import timedelta
# 创建记录器logger
logger = logging.getLogger('DevicePassword')
logger.setLevel(logging.DEBUG)
# 创建文件处理器FileHandler
ch_file = logging.FileHandler('logger.log', encoding='utf-8') # 输出到文件
ch_file.setLevel(logging.INFO)
ch_Terminal = logging.StreamHandler() # 输出到屏幕
ch_Terminal.setLevel(logging.INFO)
# 创建格式器Formatter
formater = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 配置logger
ch_file.setFormatter(formater)
ch_Terminal.setFormatter(formater)
logger.addHandler(ch_file)
logger.addHandler(ch_Terminal)
appname = 'app.exe' # 你的程序文件名称
titlename = 'PasswordGen' # 程序主窗口的标题名称,如果不知道,可通过Spy++获取。
def startapp(name):
"""
启动外部EXE程序
:param name: 程序名称
:return: None
"""
if exe_is_active(name): # 若外部程序已存在,则终止外部程序。
os.system(r'taskkill /F /IM {}'.format(name))
time.sleep(2)
if os.path.exists(name): # 非阻塞启动外部EXE程序。
subprocess.Popen([name])
else:
print("软件不存在,请将{}复制到程序相同目录下。".format(appname))
def closeapp(name):
"""
关闭外部EXE程序
:param name:
:return:
"""
if exe_is_active(name): # 若外部程序已存在,则终止外部程序。
os.system(r'taskkill /F /IM {}'.format(name))
time.sleep(2)
def exe_is_active(name):
"""
判断外部EXE程序是否已经启动
:param name: 外部程序名
:return: TRUE or FALSE
"""
processes_name = []
pids = psutil.pids()
for pid in pids:
p = psutil.Process(pid)
processes_name.append(p.name())
if name in processes_name:
print('{} 已存在'.format(name))
return True
else:
print('{} 不存在'.format(name))
return False
def click_generate(btngenerate):
"""
点击Generate按钮,生成密码
:param btngenerate: 按钮句柄
:return:None
"""
win32gui.SendMessage(btngenerate, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, 0)
win32gui.SendMessage(btngenerate, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, 0)
def find_control(maintitlename):
hwnd = win32gui.FindWindow(None, maintitlename) # 获取主窗口句柄
hwnd_child_list = [] # 子控件数组,存储子控件
if hwnd > 0:
win32gui.EnumChildWindows(hwnd, lambda hwnd, param: param.append(hwnd), hwnd_child_list)
return hwnd_child_list # 返回子控件数组
def get_edit(edit_hwnd):
"""
获取密码控件内容
:param edit_hwnd: 密码控件句柄
:return: 控件内容text
"""
len_text = win32gui.SendMessage(edit_hwnd, win32con.WM_GETTEXTLENGTH) + 1 # 获取文本内容长度
buffer = win32gui.PyMakeBuffer(len_text) # 初始化缓存
win32gui.SendMessage(edit_hwnd, win32con.WM_GETTEXT, len_text, buffer) # 发送消息,获取控件内容
address, length = win32gui.PyGetBufferAddressAndLen(buffer) # 转换缓存内容
text = win32gui.PyGetString(address, length - 1)
return text
def sync_cb_device_password(pwd): # 将获取的密码更新到密码表中,供设备使用。这样就不用我每次手动更新了。
sql = 'UPDATE device set DeviceUserName= %s , DevicePassword = %s'
params = ('admin', pwd)
with DBMysql(log_time=False) as db: # DBMysql自定义数据库类,执行SQL。
db.cursor.execute(sql, params)
data = db.cursor.rowcount
print('同步设备密码-sync_device_password:{},更改记录数量:{}'.format(pwd, data))
if not data or data is None or data == 0 or data > 1:
isset = False
else:
isset = True
return isset
def main():
startapp(appname)
time.sleep(5)
# 获取窗体控件
handle = find_control(titlename)
# 点击按钮,生成密码
click_generate(handle[5]) # handle[5] 是按钮的句柄,通过Spy++获取。 这里应该可以通过控件名称获取,以后优化。
# 获取密码内容
password = get_edit(handle[4]) # handle[4] 是按钮的句柄,通过Spy++获取。 这里应该可以通过控件名称或类名获取,以后优化。
print("密码:{}".format(password))
time.sleep(3)
sync_cb_device_password(password)
closeapp(appname)
logger.info('今天的密码是:{}'.format(password))