程序调用Excel组件时报错-2147024703 Automation error %1 is not a valid Win32 > application或者-2147024894

程序代码中调用excel 使用方法CreateObject(“EXCEL.Application”)或者引用方法.在有些机器虽然安装excel但可能还会报错,-2147024703 Automation或-2147024894
  
  这种情况,可以检查一下注册表EXCEL.Application对象对应的clsid;v00024500-0000-0000-C000-000000000046 在注册表搜索这个clsid即可,32位和64位的操作系统有一点差别,64位多了Wow6432Node这个项
我的64位系统是这样的HKEY_CLASSES_ROOT\Wow6432Node\CLSID{00024500-0000-0000-C000-000000000046这个项下面有LocalServer和LocalServer32这两个子项下的默认值.它应该是指向excel程序所在的全路径,两个都修改正确就可以了.

你只要把它的值复制到"运行"中,运行一下,如果excel程序正常启动了,就说明他是正确的. 如果你的机器是标题提示的那样错误信息,这时excel不会正常启动.但你会发现这个路径可能是完全正确的,你要注意的是这个路径是长文件名格式,这里需要我们把它变成短文件名格式也就是就是 8.3 的文件格式路径就可以了.
  
  我这里是这样的
  C:\Program Files\office2016\Office16\EXCEL.EXE /automation
  改成
  C:\PROGRA~1\office2016\Office16\EXCEL.EXE /automation
  
  修改后检查是否正确,只在"运行"中 运行一下能正常启动Excel就可以了,问题解决.
  (注: 8.3格式的第7 i和8位字符,分别是1,但第8位,不一定是1,也可能是23,这要看你文件夹名称前6位相同的有多少,这时你要使用命令行的方式确认一下就可以了)

发送完回车键之后,宏执行的下一步会进行报告路径选择,同样是一个窗口,但是程序还是在一直发送回车键 import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import threading import queue import traceback import webbrowser import datetime import configparser import win32com.client import pythoncom import win32gui import win32con import win32api class DiffProcessorApp: def __init__(self, root): self.root = root root.title("ファイル比較実施工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") self.style = ttk.Style() self.style.theme_use('clam') self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('View.TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#008000", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('View.TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('active', '#004D00'), ('disabled', '#808080')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="ファイル比較実施工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) self.old_folder_entry, _ = self.create_folder_selector(file_frame, "原始文件夹:", "old_folder") self.new_folder_entry, _ = self.create_folder_selector(file_frame, "修改后文件夹:", "new_folder") options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) self.excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") self.excel_frame.pack(fill=tk.X, pady=5) ttk.Label(self.excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(self.excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, "excel_file", [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) winmerge_frame = ttk.Frame(self.excel_frame) winmerge_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(winmerge_frame, text="WinMerge路径:").grid(row=0, column=0, sticky=tk.W) self.winmerge_entry = ttk.Entry(winmerge_frame, width=60) self.winmerge_entry.grid(row=0, column=1, padx=5) self.winmerge_entry.insert(0, r"E:\App\WinMerge\WinMerge2.16.46.0\WinMergeU.exe") ttk.Button(winmerge_frame, text="浏览...", command=lambda: self.select_file(self.winmerge_entry, "winmerge_path", [("WinMerge 可执行文件", "*.exe")])).grid(row=0, column=2) delete_frame = ttk.Frame(self.excel_frame) delete_frame.grid(row=2, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) self.delete_temp_files_var = tk.BooleanVar(value=False) delete_check = ttk.Checkbutton( delete_frame, text="完成后删除临文件", variable=self.delete_temp_files_var, command=self.update_view_button_state ) delete_check.grid(row=0, column=0, padx=5, sticky=tk.W) self.delete_status_label = ttk.Label( delete_frame, text="(勾选后将删除报告文件,无法查看)", foreground="#FF0000", font=("Segoe UI", 9) ) self.delete_status_label.grid(row=0, column=1, padx=5) button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) self.view_folder_report_button = ttk.Button(button_frame, text="查看文件夹报告", command=lambda: self.view_report("folder"), width=15, state=tk.DISABLED, style='View.TButton') self.view_folder_report_button.pack(side=tk.LEFT, padx=10) self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) self.processing = False self.queue = queue.Queue() self.folder_report_path = None self.files_dir = None self.copied_html_files = [] self.config_file = "folder_compare_config.ini" self.load_paths() self.root.after(100, self.process_queue) def update_view_button_state(self): if self.delete_temp_files_var.get(): self.view_folder_report_button.config(state=tk.DISABLED) self.style.configure('View.TButton', foreground='#808080', background='#BDBDBD') else: if self.folder_report_path and os.path.exists(self.folder_report_path): self.view_folder_report_button.config(state=tk.NORMAL) self.style.configure('View.TButton', foreground='#008000', background='#4CAF50') else: self.view_folder_report_button.config(state=tk.DISABLED) self.style.configure('View.TButton', foreground='#808080', background='#BDBDBD') self.view_folder_report_button.configure(style='View.TButton') def create_folder_selector(self, parent, label_text, config_key): frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry, config_key)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry, config_key): initial_dir = self.get_last_path(config_key) if not initial_dir or not os.path.exists(initial_dir): initial_dir = os.getcwd() foldername = filedialog.askdirectory(initialdir=initial_dir) if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) self.populate_folder_tree(foldername) self.save_path(config_key, foldername) def select_file(self, entry, config_key, filetypes=None): if filetypes is None: filetypes = [("所有文件", "*.*")] initial_dir = self.get_last_path(config_key) if not initial_dir or not os.path.exists(initial_dir): initial_dir = os.getcwd() if os.path.isfile(initial_dir): initial_dir = os.path.dirname(initial_dir) filename = filedialog.askopenfilename(filetypes=filetypes, initialdir=initial_dir) if filename: entry.delete(0, tk.END) entry.insert(0, filename) self.save_path(config_key, filename) def get_last_path(self, config_key): config = configparser.ConfigParser() if os.path.exists(self.config_file): config.read(self.config_file) if config.has_option('Paths', config_key): return config.get('Paths', config_key) return None def populate_folder_tree(self, path): self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): self.queue.put(("log", message)) def update_progress(self, value): self.queue.put(("progress", value)) def update_status(self, message): self.queue.put(("status", message)) def process_queue(self): try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def view_report(self, report_type): if report_type == "folder" and self.folder_report_path and os.path.exists(self.folder_report_path): try: webbrowser.open(self.folder_report_path) except Exception as e: messagebox.showerror("错误", f"无法打开文件夹报告: {str(e)}") else: messagebox.showwarning("警告", f"没有可用的{report_type}报告文件") def process_folders(self, old_path, new_path, excel_file): try: report_dir = os.path.dirname(excel_file) or os.getcwd() os.makedirs(report_dir, exist_ok=True) timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") self.folder_report_path = os.path.join(report_dir, f"folder_diff_report_{timestamp}.html") self.update_status("生成比较报告...") self.update_progress(30) winmerge_path = self.winmerge_entry.get() if not self.run_winmerge(winmerge_path, old_path, new_path): self.update_status("WinMerge执行失败") return if not os.path.exists(self.folder_report_path) or os.path.getsize(self.folder_report_path) == 0: self.log_message("警告: 文件夹报告为空或未生成") else: self.log_message(f"文件夹报告生成成功: {self.folder_report_path} ({os.path.getsize(self.folder_report_path)} bytes)") self.copy_detail_files(report_dir) self.update_status("打开Excel文件...") self.update_progress(80) if not self.open_excel_file(excel_file): self.update_status("打开Excel失败") return if self.delete_temp_files_var.get(): self.delete_winmerge_reports() self.log_message("已删除所有临文件") else: self.log_message("保留临文件,可查看报告") self.root.after(100, self.update_view_button_state) self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "已生成报告并打开Excel文件") except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: if self.processing: self.stop_processing() def delete_winmerge_reports(self): if not self.folder_report_path: return if os.path.exists(self.folder_report_path): try: os.remove(self.folder_report_path) self.log_message(f"已删除报告文件: {self.folder_report_path}") except Exception as e: self.log_message(f"删除报告文件失败: {str(e)}") base_path = os.path.splitext(self.folder_report_path)[0] files_dir = base_path + ".files" if os.path.exists(files_dir): try: shutil.rmtree(files_dir) self.log_message(f"已删除.files目录: {files_dir}") except Exception as e: self.log_message(f"删除.files目录失败: {str(e)}") report_dir = os.path.dirname(self.folder_report_path) if os.path.exists(report_dir) and hasattr(self, 'copied_html_files') and self.copied_html_files: deleted_count = 0 for file_path in self.copied_html_files: if os.path.exists(file_path): try: os.remove(file_path) deleted_count += 1 self.log_message(f"已删除复制的HTML文件: {file_path}") except Exception as e: self.log_message(f"删除HTML文件失败: {file_path} - {str(e)}") self.log_message(f"已删除 {deleted_count}/{len(self.copied_html_files)} 个复制的HTML文件") self.copied_html_files = [] self.folder_report_path = None self.view_folder_report_button.config(state=tk.DISABLED) self.root.after(100, self.update_view_button_state) def copy_detail_files(self, report_dir): base_path = os.path.splitext(self.folder_report_path)[0] files_dir = base_path + ".files" if not os.path.exists(files_dir): self.log_message(f"警告: 详细文件目录不存在 {files_dir}") return html_files = [f for f in os.listdir(files_dir) if f.lower().endswith('.html')] if not html_files: self.log_message(f"警告: 详细文件目录中没有HTML文件 {files_dir}") return if not hasattr(self, 'copied_html_files') or not self.copied_html_files: self.copied_html_files = [] copied_count = 0 for file_name in html_files: src_path = os.path.join(files_dir, file_name) dst_path = os.path.join(report_dir, file_name) try: shutil.copy2(src_path, dst_path) copied_count += 1 self.copied_html_files.append(dst_path) except Exception as e: self.log_message(f"复制文件失败: {file_name} - {str(e)}") self.log_message(f"已复制 {copied_count}/{len(html_files)} 个详细HTML文件到报告目录") def start_processing(self): if self.processing: self.log_message("警告: 处理正在进行中") return old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") winmerge_path = self.winmerge_entry.get() if not winmerge_path or not os.path.exists(winmerge_path): validation_errors.append("WinMerge路径无效或未设置") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.view_folder_report_button.config(state=tk.DISABLED) self.processing = True self.copied_html_files = [] thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def run_winmerge(self, winmerge_path, path1, path2): if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False os.makedirs(os.path.dirname(self.folder_report_path), exist_ok=True) cmd = [ winmerge_path, '/u', '/nosplash', '/dl', 'Base', '/dr', 'Modified', '/noninteractive', '/minimize' ] if self.recursive_var.get(): cmd.extend(['/r', '/s']) else: cmd.extend(['/r-', '/s-']) file_filter = self.filter_var.get() if file_filter and file_filter != "*.*": cmd.extend(['-f', file_filter]) cmd.extend(['/or', self.folder_report_path]) cmd.extend([path1, path2]) self.update_status("正在生成比较报告...") return self.execute_winmerge_command(cmd, "比较报告") def execute_winmerge_command(self, cmd, report_type): try: self.log_message(f"开始生成{report_type}...") self.log_message(f"执行命令: {' '.join(cmd)}") start_time = time.time() creation_flags = 0 if os.name == 'nt': creation_flags = subprocess.CREATE_NO_WINDOW process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace', creationflags=creation_flags ) timeout = 900 try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() self.log_message(f"{report_type}生成超({timeout}秒),已终止进程") return False elapsed = time.time() - start_time self.log_message(f"{report_type}生成完成,耗: {elapsed:.2f}秒") if stdout.strip(): self.log_message(f"WinMerge输出:\n{stdout[:2000]}") if stderr.strip(): self.log_message(f"WinMerge错误:\n{stderr[:1000]}") if process.returncode == 0: self.log_message(f"{report_type}命令执行成功") return True elif process.returncode == 1: self.log_message(f"{report_type}命令执行完成(发现差异)") return True elif process.returncode == 2: self.log_message(f"{report_type}命令执行完成(发现错误)") return False else: error_msg = f"{report_type}生成失败(退出码{process.returncode})" self.log_message(error_msg) return False except Exception as e: self.log_message(f"{report_type}生成错误: {str(e)}\n{traceback.format_exc()}") return False def send_enter_key(): # 查找活动窗口(通常是弹出的对话框) hwnd = win32gui.GetForegroundWindow() if hwnd: # 发送Enter键 win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0) win32api.PostMessage(hwnd, win32con.WM_KEYUP, win32con.VK_RETURN, 0) def open_excel_file(self, excel_path): self.log_message("正在后台自动化处理Excel文件...") # 初始化COM库 pythoncom.CoInitialize() try: if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False excel_app = None try: # 创建Excel应用程序对象 excel_app = win32com.client.Dispatch("Excel.Application") excel_app.Visible = True excel_app.WindowState = -4140 # win32com.client.constants.xlMinimized excel_app.DisplayAlerts = False excel_app.ScreenUpdating = False excel_app.EnableEvents = False # 打开工作簿 wb = excel_app.Workbooks.Open(os.path.abspath(excel_path)) self.log_message(f"Excel文件已打开: {excel_path}") # 自动执行宏并处理后续交互 success = self.execute_macro_with_automation(excel_app, wb) # 保存并关闭工作簿 if success: wb.Close(SaveChanges=True) self.log_message("Excel文件处理完成并保存") else: wb.Close(SaveChanges=False) self.log_message("宏执行失败,已放弃更改") return success except Exception as e: error_msg = f"Excel自动化失败: {str(e)}" self.log_message(error_msg) self.log_message(traceback.format_exc()) return False finally: # 确保释放Excel对象 if excel_app is not None: try: excel_app.Quit() # 确保Excel进程完全退出 time.sleep(1) # 额外的清理操作 del excel_app except: pass finally: # 确保取消初始化COM库 pythoncom.CoUninitialize() def execute_macro_with_automation(self, excel_app, workbook): """异步执行宏并监控对话框""" try: # 获取Excel主窗口句柄 hwnd = self.find_excel_window(excel_app) if not hwnd: self.log_message("警告: 无法获取Excel窗口句柄") return False # 创建事件标志用于线程通信 self.macro_finished = threading.Event() # 启动宏执行监控线程 monitor_thread = threading.Thread( target=self.monitor_macro_execution, args=(excel_app, workbook, hwnd) ) monitor_thread.daemon = True monitor_thread.start() # 执行宏(会阻塞当前线程) try: self.log_message("开始执行宏...") excel_app.Run("Sheet1.ExposedMacro") self.log_message("宏执行完成") except Exception as e: self.log_message(f"宏执行异常: {str(e)}") finally: # 标记宏执行完成 self.macro_finished.set() # 等待监控线程完成或超 monitor_thread.join(timeout=30) # 最多等待30秒 if monitor_thread.is_alive(): self.log_message("警告: 宏监控线程超") return True except Exception as e: self.log_message(f"宏执行框架错误: {str(e)}\n{traceback.format_exc()}") return False def monitor_macro_execution(self, excel_app, workbook, hwnd): """后台监控宏执行状态并处理对话框""" self.log_message("启动宏执行监控线程...") # 等待宏开始执行(最多10秒) start_time = time.time() macro_started = False while not macro_started and time.time() - start_time < 10: # 尝试检测宏是否已开始执行 if self.is_macro_running(excel_app): macro_started = True self.log_message("检测到宏已开始执行") time.sleep(0.5) if not macro_started: self.log_message("警告: 未检测到宏执行启动") return # 主监控循环 - 最多运行2分钟 start_time = time.time() while not self.macro_finished.is_set() and time.time() - start_time < 120: try: # 检查对话框是否存在 dialog_hwnd = self.find_dialog_window(hwnd) if dialog_hwnd: self.log_message(f"检测到对话框窗口: 0x{dialog_hwnd:X}") if self.click_dialog_button(dialog_hwnd): self.log_message("成功点击对话框按钮") time.sleep(1) # 给对话框关闭间 else: self.force_confirm_dialog(dialog_hwnd) except Exception as e: self.log_message(f"对话框监控错误: {str(e)}") # 短暂休眠避免CPU占用过高 time.sleep(0.5) if self.macro_finished.is_set(): self.log_message("宏执行监控正常结束") else: self.log_message("警告: 宏执行监控超") def find_excel_window(self, excel_app): """获取Excel应用程序窗口句柄""" try: # 方法1:通过窗口标题匹配 titles = [] def enum_windows_callback(hwnd, results): if win32gui.IsWindowVisible(hwnd): title = win32gui.GetWindowText(hwnd) # 匹配Excel主窗口标题的模式(通常包含"Excel") if "Excel" in title and "Book" in title: results.append(hwnd) win32gui.EnumWindows(enum_windows_callback, titles) if titles: return titles[0] # 返回第一个匹配的窗口 # 方法2:如果没有找到,尝试通过类名 class_names = [] def enum_class_callback(hwnd, results): if win32gui.IsWindowVisible(hwnd): class_name = win32gui.GetClassName(hwnd) if class_name == "XLMAIN": results.append(hwnd) win32gui.EnumWindows(enum_class_callback, class_names) if class_names: return class_names[0] # 方法3:作为备选,尝试获取活动窗口 hwnd = win32gui.GetForegroundWindow() if hwnd: title = win32gui.GetWindowText(hwnd) if "Excel" in title: return hwnd self.log_message("警告: 未找到Excel窗口") return None except Exception as e: self.log_message(f"查找Excel窗口失败: {str(e)}") return None def is_macro_running(self, excel_app): """ 检测宏是否正在运行 改进:通过检测Excel是否处于交互模式(有对话框打开) """ try: # 尝试访问Excel的Ready属性 - 如果宏正在运行,Excel通常不处于就绪状态 if excel_app.Ready: # 检查是否有打开的对话框 if excel_app.Dialogs.Count > 0: return True return False return True except: # 如果访问失败,假设宏仍在运行 return True def find_dialog_window(self, parent_hwnd): """查找Excel的对话框窗口""" try: dialogs = [] # 定义回调函数枚举子窗口 def enum_child_windows(hwnd, results): if win32gui.IsWindowVisible(hwnd): class_name = win32gui.GetClassName(hwnd) # 常见对话框类名 if class_name in ["#32770", "bosa_sdm_XL9", "EXCEL7"]: results.append(hwnd) # 先尝试在父窗口内查找 win32gui.EnumChildWindows(parent_hwnd, enum_child_windows, dialogs) if dialogs: return dialogs[0] # 如果未找到,尝试在所有顶层窗口中查找 all_dialogs = [] win32gui.EnumWindows( lambda hwnd, results: results.append(hwnd) if win32gui.IsWindowVisible(hwnd) and win32gui.GetClassName(hwnd) in ["#32770", "bosa_sdm_XL9", "EXCEL7"] else None, all_dialogs ) return all_dialogs[0] if all_dialogs else None except Exception as e: self.log_message(f"查找对话框失败: {str(e)}") return None def click_dialog_button(self, dialog_hwnd): """点击对话框的确定按钮""" try: # 查找按钮控件 button_hwnd = None # 枚举对话框的子窗口查找按钮 buttons = [] win32gui.EnumChildWindows( dialog_hwnd, lambda hwnd, results: results.append(hwnd) if "Button" in win32gui.GetClassName(hwnd) else None, buttons ) # 查找文本为"确定"或"OK"的按钮 for btn in buttons: text = win32gui.GetWindowText(btn) if "确定" in text or "OK" in text: button_hwnd = btn break if button_hwnd: # 发送点击消息 win32gui.PostMessage(button_hwnd, win32con.BM_CLICK, 0, 0) self.log_message("已发送按钮点击消息") return True # 如果未找到特定按钮,尝试点击第一个按钮 if buttons: win32gui.PostMessage(buttons[0], win32con.BM_CLICK, 0, 0) self.log_message("已点击第一个按钮") return True return False except Exception as e: self.log_message(f"按钮点击失败: {str(e)}") return False def force_confirm_dialog(self, hwnd): """强制确认对话框的最后手段""" try: # 激活窗口 win32gui.SetForegroundWindow(hwnd) time.sleep(0.5) # 方法1:发送回车键 win32api.keybd_event(win32con.VK_RETURN, 0, 0, 0) win32api.keybd_event(win32con.VK_RETURN, 0, win32con.KEYEVENTF_KEYUP, 0) self.log_message("已发送回车键") time.sleep(0.5) return True except Exception as e: self.log_message(f"强制确认失败: {str(e)}") return False def stop_processing(self): self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.root.after(100, self.update_view_button_state) self.update_status("操作已停止") def load_paths(self): config = configparser.ConfigParser() if os.path.exists(self.config_file): try: config.read(self.config_file) if config.has_option('Paths', 'old_folder'): old_path = config.get('Paths', 'old_folder') self.old_folder_entry.delete(0, tk.END) self.old_folder_entry.insert(0, old_path) if os.path.isdir(old_path): self.populate_folder_tree(old_path) if config.has_option('Paths', 'new_folder'): new_path = config.get('Paths', 'new_folder') self.new_folder_entry.delete(0, tk.END) self.new_folder_entry.insert(0, new_path) if config.has_option('Paths', 'excel_file'): excel_path = config.get('Paths', 'excel_file') self.excel_file_entry.delete(0, tk.END) self.excel_file_entry.insert(0, excel_path) if config.has_option('Paths', 'winmerge_path'): winmerge_path = config.get('Paths', 'winmerge_path') self.winmerge_entry.delete(0, tk.END) self.winmerge_entry.insert(0, winmerge_path) self.log_message("已加载上次保存的路径") except Exception as e: self.log_message(f"加载配置文件失败: {str(e)}") else: self.log_message("未找到配置文件,将使用默认路径") def save_path(self, key, path): config = configparser.ConfigParser() if os.path.exists(self.config_file): config.read(self.config_file) if not config.has_section('Paths'): config.add_section('Paths') config.set('Paths', key, path) try: with open(self.config_file, 'w') as configfile: config.write(configfile) self.log_message(f"已保存路径: {key} = {path}") except Exception as e: self.log_message(f"保存路径失败: {str(e)}") def save_all_paths(self): config = configparser.ConfigParser() config.add_section('Paths') config.set('Paths', 'old_folder', self.old_folder_entry.get()) config.set('Paths', 'new_folder', self.new_folder_entry.get()) config.set('Paths', 'excel_file', self.excel_file_entry.get()) config.set('Paths', 'winmerge_path', self.winmerge_entry.get()) try: with open(self.config_file, 'w') as configfile: config.write(configfile) self.log_message("所有路径已保存") except Exception as e: self.log_message(f"保存所有路径失败: {str(e)}") def on_closing(self): self.save_all_paths() self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop()
最新发布
09-10
小蓝窗:“PS C:\Users\Administrator> $response = Invoke-CurlRequest -Url "https://invalid-url.example" -OutputType Object Invoke-CurlRequest : curl 鍛戒护澶辫触 (閫€鍑虹爜: 6): curl: (6) Could not resolve host: invalid-url.example 所在位置 行:1 字符: 13 + $response = Invoke-CurlRequest -Url "https://invalid-url.example" -Ou ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Invoke-CurlRequest PS C:\Users\Administrator> $response | Format-List Name : Status Value : Error Name : Url Value : https://invalid-url.example Name : ErrorMessage Value : curl 鍛戒护澶辫触 (閫€鍑虹爜: 6): curl: (6) Could not resolve host: invalid-url.example Name : Method Value : GET PS C:\Users\Administrator> PS C:\Users\Administrator> # 预期输出: PS C:\Users\Administrator> # Status : Error PS C:\Users\Administrator> # ErrorMessage : curl 命令失败 (退出码: 6): curl: (6) Could not resolve host: invalid-url.example PS C:\Users\Administrator> # Url : https://invalid-url.example PS C:\Users\Administrator> # Method : GET PS C:\Users\Administrator> $response = Invoke-CurlRequest -Url "https://example.com" -OutputType Object Invoke-CurlRequest : 无效的 JSON 基元: 。 所在位置 行:1 字符: 13 + $response = Invoke-CurlRequest -Url "https://example.com" -OutputType ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Invoke-CurlRequest PS C:\Users\Administrator> $response | Format-List Name : Status Value : Error Name : Url Value : https://example.com Name : ErrorMessage Value : 无效的 JSON 基元: 。 Name : Method Value : GET PS C:\Users\Administrator> PS C:\Users\Administrator> # 预期输出: PS C:\Users\Administrator> # Status : NonJsonResponse PS C:\Users\Administrator> # Content : <!doctype html>... (完整的HTML内容) PS C:\Users\Administrator> $response = Invoke-CurlRequest -Url "https://httpbin.org/get" -OutputType Object PS C:\Users\Administrator> $response.headers.'User-Agent' Mozilla/5.0 (Windows NT 10.0; Win64; x64) Curl/8.15.0 PS C:\Users\Administrator> PS C:\Users\Administrator> # 预期输出: PS C:\Users\Administrator> # Mozilla/5.0 (Windows NT 10.0; Win64; x64) Curl/8.15.0 PS C:\Users\Administrator> $response = Invoke-CurlRequest -Url "https://httpbin.org/status/404" -OutputType Object Invoke-CurlRequest : curl 鍛戒护澶辫触 (閫€鍑虹爜: 22): curl: (22) The requested URL returned error: 404 所在位置 行:1 字符: 13 + $response = Invoke-CurlRequest -Url "https://httpbin.org/status/404" ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Invoke-CurlRequest PS C:\Users\Administrator> $response | Format-List Name : Status Value : Error Name : Url Value : https://httpbin.org/status/404 Name : ErrorMessage Value : curl 鍛戒护澶辫触 (閫€鍑虹爜: 22): curl: (22) The requested URL returned error: 404 Name : Method Value : GET PS C:\Users\Administrator> PS C:\Users\Administrator> # 预期输出: PS C:\Users\Administrator> # Status : Error PS C:\Users\Administrator> # ErrorMessage : curl 命令失败 (退出码: 22): PS C:\Users\Administrator> # Url : https://httpbin.org/status/404 PS C:\Users\Administrator> # Method : GET PS C:\Users\Administrator>” CurlTools.psm1改成了“function Get-CurlVersion { <# .SYNOPSIS 获取 curl 版本信息 #> try { $result = curl.exe --version 2>&1 return $result } catch { return "curl 未安装。PowerShell 版本: $($PSVersionTable.PSVersion)" } } function Invoke-CurlRequest { <# .SYNOPSIS 执行 curl 请求 .PARAMETER Url 请求的URL .PARAMETER Method HTTP方法 (GET, POST, PUT, DELETE等) .PARAMETER Headers 请求头,格式为 @{Header1="Value1"; Header2="Value2"} .PARAMETER Body 请求体内容 .PARAMETER ContentType 请求体内容类型(默认为 application/json) .PARAMETER OutputType 输出类型: Raw (原始文本) 或 Object (解析后的对象) #> param( [Parameter(Mandatory=$true)] [string]$Url, [string]$Method = "GET", [hashtable]$Headers = @{}, [string]$Body, [string]$ContentType = "application/json", [ValidateSet("Raw", "Object")] [string]$OutputType = "Raw" ) # 构建 curl 命令参数 $curlArgs = @( $Url, "-X", $Method, "--silent", "--show-error", "--fail" # 添加 --fail 参数以在HTTP错误返回非零退出码 ) # 添加内容类型头 if (-not $Headers.ContainsKey("Content-Type")) { $Headers["Content-Type"] = $ContentType } # 添加请求头 foreach ($key in $Headers.Keys) { $curlArgs += "-H" $curlArgs += "$key`: $($Headers[$key])" } # 添加请求体 if ($Body) { $curlArgs += "-d" $curlArgs += $Body } try { # 执行 curl 命令并捕获输出和错误 $rawOutput = curl.exe @curlArgs 2>&1 $curlExitCode = $LASTEXITCODE # 检查 curl 命令是否成功 if ($curlExitCode -ne 0) { # 提取错误信息并确保使用英文 $errorMessage = if ($rawOutput -is [System.Management.Automation.ErrorRecord]) { $rawOutput.Exception.Message } else { # 使用英文错误消息避免乱码 $englishError = curl.exe @curlArgs --stderr - 2>&1 | Where-Object { $_ -match 'curl: \(\d+\)' } if ($englishError) { $englishError } else { $rawOutput -join "`n" } } throw "curl command failed (exit code: $curlExitCode): $errorMessage" } if ($OutputType -eq "Object") { # 尝试解析 JSON 响应 try { $responseObj = $rawOutput | ConvertFrom-Json -ErrorAction Stop # 智能提取请求体数据 if ($responseObj.PSObject.Properties.Name -contains "json") { return $responseObj.json } elseif ($responseObj.PSObject.Properties.Name -contains "data") { # 尝试解析 data 字段中的 JSON try { return $responseObj.data | ConvertFrom-Json } catch { return $responseObj.data } } return $responseObj } catch { # 无法解析为 JSON,返回非 JSON 响应对象 return [PSCustomObject]@{ Status = "NonJsonResponse" Content = $rawOutput } } } else { return $rawOutput } } catch { # 返回错误对象 return [PSCustomObject]@{ Status = "Error" ErrorMessage = $_.Exception.Message Url = $Url Method = $Method } } } Export-ModuleMember -Function Get-CurlVersion, Invoke-CurlRequest ”
08-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值