基于Python編寫windows電腦用戶操作記錄查看器
軟件功能
讀取系統(tǒng)現(xiàn)有的日志記錄:
Windows系統(tǒng)事件日志
最近訪問的文件記錄
程序安裝和執(zhí)行記錄
刷新日志、搜索記錄、刪除選中記錄
軟件截圖
核心源碼
import tkinter as tk from tkinter import ttk, messagebox import os import winreg from datetime import datetime from win32com.shell import shell, shellcon import win32api import win32con import win32evtlog import win32evtlogutil import win32file import struct from ctypes import * import sys import ctypes def is_admin(): """檢查是否具有管理員權(quán)限""" try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False class PCActivityMonitor: def __init__(self, root): self.root = root self.root.title("用戶操作記錄查看器") self.root.geometry("1200x800") # 檢查管理員權(quán)限 if not is_admin(): messagebox.showwarning("警告", "程序未以管理員權(quán)限運(yùn)行,某些功能可能受限。\n" "建議以管理員身份重新運(yùn)行程序以獲取完整功能。") # 創(chuàng)建主框架 self.main_frame = ttk.Frame(root) self.main_frame.pack(expand=True, fill="both", padx=5, pady=5) # 創(chuàng)建工具欄 self.create_toolbar() # 創(chuàng)建Treeview self.create_treeview() # 初始加載數(shù)據(jù) self.load_all_logs() def create_toolbar(self): """創(chuàng)建工具欄""" toolbar = ttk.Frame(self.main_frame) toolbar.pack(fill="x", padx=5, pady=5) # 刷新按鈕 ttk.Button(toolbar, text="刷新", command=self.load_all_logs).pack(side="left", padx=5) # 刪除按鈕 ttk.Button(toolbar, text="刪除選中記錄", command=self.delete_selected).pack(side="left", padx=5) # 導(dǎo)出按鈕 ttk.Button(toolbar, text="導(dǎo)出記錄", command=self.export_logs).pack(side="left", padx=5) # 搜索框 ttk.Label(toolbar, text="搜索:").pack(side="left", padx=5) self.search_var = tk.StringVar() ttk.Entry(toolbar, textvariable=self.search_var, width=30).pack(side="left", padx=5) ttk.Button(toolbar, text="搜索", command=self.search_logs).pack(side="left", padx=5) ttk.Button(toolbar, text="清除搜索", command=self.clear_search).pack(side="left", padx=5) def create_treeview(self): """創(chuàng)建Treeview控件""" frame = ttk.Frame(self.main_frame) frame.pack(expand=True, fill="both") # 創(chuàng)建Treeview和滾動(dòng)條 self.tree = ttk.Treeview(frame, columns=("time", "action", "object", "path"), show="headings") # 設(shè)置列標(biāo)題 self.tree.heading("time", text="操作時(shí)間", command=lambda: self.treeview_sort_column("time", False)) self.tree.heading("action", text="操作類型", command=lambda: self.treeview_sort_column("action", False)) self.tree.heading("object", text="操作對(duì)象", command=lambda: self.treeview_sort_column("object", False)) self.tree.heading("path", text="完整路徑", command=lambda: self.treeview_sort_column("path", False)) # 設(shè)置列寬 self.tree.column("time", width=150) self.tree.column("action", width=100) self.tree.column("object", width=250) self.tree.column("path", width=400) # 添加滾動(dòng)條 y_scrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) x_scrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=y_scrollbar.set, xscrollcommand=x_scrollbar.set) # 布局 self.tree.pack(side="left", fill="both", expand=True) y_scrollbar.pack(side="right", fill="y") x_scrollbar.pack(side="bottom", fill="x") # 綁定右鍵菜單 self.tree.bind("<Button-3>", self.show_context_menu) # 綁定雙擊事件 self.tree.bind("<Double-1>", self.on_double_click) def load_all_logs(self): """加載所有日志記錄""" self.tree.delete(*self.tree.get_children()) # 創(chuàng)建臨時(shí)列表存儲(chǔ)所有記錄 all_records = [] # 獲取文件訪問記錄 all_records.extend(self.get_file_access_logs()) # 獲取程序執(zhí)行記錄 all_records.extend(self.get_program_execution_logs()) # 獲取應(yīng)用程序日志 all_records.extend(self.get_application_logs()) # 按時(shí)間倒序排序 all_records.sort(key=lambda x: x[0], reverse=True) # 插入排序后的記錄 for record in all_records: self.tree.insert("", "end", values=record) def get_file_access_logs(self): """獲取文件訪問記錄""" records = [] try: # 讀取最近訪問文件 recent_path = os.path.join(os.environ['USERPROFILE'], 'AppData\\Roaming\\Microsoft\\Windows\\Recent') for file in os.listdir(recent_path): if file.endswith('.lnk'): file_path = os.path.join(recent_path, file) access_time = datetime.fromtimestamp(os.path.getatime(file_path)) try: shortcut = shell.SHCreateItemFromParsingName( file_path, None, shell.IID_IShellItem ) target_path = shortcut.GetDisplayName(shellcon.SIGDN_FILESYSPATH) records.append(( access_time.strftime("%Y-%m-%d %H:%M:%S"), "文件訪問", file[:-4], target_path )) except: continue # 讀取Office文檔記錄 office_path = os.path.join(os.environ['APPDATA'], 'Microsoft\\Office\\Recent') if os.path.exists(office_path): for file in os.listdir(office_path): if file.endswith('.lnk'): file_path = os.path.join(office_path, file) access_time = datetime.fromtimestamp(os.path.getatime(file_path)) records.append(( access_time.strftime("%Y-%m-%d %H:%M:%S"), "Office文檔", file[:-4], file_path )) except Exception as e: messagebox.showerror("錯(cuò)誤", f"讀取文件訪問記錄出錯(cuò): {str(e)}") return records def get_program_execution_logs(self): """獲取程序執(zhí)行記錄""" records = [] try: key_paths = [ (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"), (winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") ] for hkey, key_path in key_paths: try: with winreg.OpenKey(hkey, key_path, 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY) as key: for i in range(winreg.QueryInfoKey(key)[0]): try: subkey_name = winreg.EnumKey(key, i) with winreg.OpenKey(key, subkey_name) as subkey: try: display_name = winreg.QueryValueEx(subkey, "DisplayName")[0] install_date = winreg.QueryValueEx(subkey, "InstallDate")[0] install_location = winreg.QueryValueEx(subkey, "InstallLocation")[0] date_obj = datetime.strptime(install_date, "%Y%m%d") records.append(( date_obj.strftime("%Y-%m-%d %H:%M:%S"), "軟件安裝", display_name, install_location )) except: continue except: continue except: continue except Exception as e: messagebox.showerror("錯(cuò)誤", f"讀取程序執(zhí)行記錄出錯(cuò): {str(e)}") return records def get_application_logs(self): """獲取應(yīng)用程序日志""" records = [] try: hand = win32evtlog.OpenEventLog(None, "Application") flags = win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ events = win32evtlog.ReadEventLog(hand, flags, 0) for event in events: if event.EventType in [win32evtlog.EVENTLOG_AUDIT_SUCCESS]: records.append(( event.TimeGenerated.Format(), "程序執(zhí)行", event.SourceName, "用戶操作記錄" )) except: pass return records def load_file_system_journal(self): """讀取文件系統(tǒng)日志""" if not is_admin(): print("需要管理員權(quán)限才能讀取文件系統(tǒng)日志") return try: drives = self.get_system_drives() for drive in drives: try: # 檢查驅(qū)動(dòng)器類型 drive_type = win32file.GetDriveType(drive + "\\") if drive_type != win32file.DRIVE_FIXED: continue # 跳過非固定磁盤 # 嘗試啟用USN日志 self.enable_usn_journal(drive) # 讀取USN日志 self.read_usn_journal(drive) except Exception as e: print(f"處理驅(qū)動(dòng)器 {drive} 時(shí)出錯(cuò): {str(e)}") continue except Exception as e: print(f"讀取文件系統(tǒng)日志出錯(cuò): {str(e)}") def get_system_drives(self): """獲取系統(tǒng)所有驅(qū)動(dòng)器""" drives = [] bitmask = win32api.GetLogicalDrives() for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': if bitmask & 1: drives.append(f'{letter}:') bitmask >>= 1 return drives def read_usn_journal(self, drive): """讀取USN日志""" try: # 打開驅(qū)動(dòng)器 handle = win32file.CreateFile( f"\\\\.\\{drive}", win32con.GENERIC_READ | win32con.GENERIC_WRITE, win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE, None, win32con.OPEN_EXISTING, 0, None ) # 獲取USN日志信息 if handle == win32file.INVALID_HANDLE_VALUE: return # 定義USN日志數(shù)據(jù)結(jié)構(gòu) class USN_JOURNAL_DATA(Structure): _fields_ = [ ("UsnJournalID", c_ulonglong), ("FirstUsn", c_ulonglong), ("NextUsn", c_ulonglong), ("LowestValidUsn", c_ulonglong), ("MaxUsn", c_ulonglong), ("MaximumSize", c_ulonglong), ("AllocationDelta", c_ulonglong), ] # 獲取USN日志信息 buf = win32file.DeviceIoControl( handle, win32file.FSCTL_QUERY_USN_JOURNAL, None, sizeof(USN_JOURNAL_DATA), None ) journal_data = USN_JOURNAL_DATA() memmove(byref(journal_data), buf, sizeof(journal_data)) # 讀取最近的USN記錄 read_data = struct.pack("QQI", journal_data.FirstUsn, journal_data.NextUsn, 0) data = win32file.DeviceIoControl( handle, win32file.FSCTL_READ_USN_JOURNAL, read_data, 8192, None ) # 解析USN記錄 usn_records = self.parse_usn_records(data) # 添加到界面 for record in usn_records: if record['reason'] & win32file.USN_REASON_FILE_DELETE: self.tree.insert("", 0, values=( record['time'].strftime("%Y-%m-%d %H:%M:%S"), "文件刪除", record['filename'], f"{drive}\\{record['filepath']}" )) win32file.CloseHandle(handle) except Exception as e: print(f"讀取USN日志出錯(cuò) {drive}: {str(e)}") def parse_usn_records(self, data): """解析USN記錄""" records = [] offset = 0 while offset < len(data): # 解析記錄頭 record_length = struct.unpack_from("I", data, offset)[0] if record_length == 0: break # 解析文件名 filename_offset = struct.unpack_from("H", data, offset + 56)[0] filename_length = struct.unpack_from("H", data, offset + 58)[0] filename = data[offset + filename_offset:offset + filename_offset + filename_length].decode('utf-16') # 獲取時(shí)間戳 timestamp = struct.unpack_from("Q", data, offset + 48)[0] time = datetime.fromtimestamp((timestamp - 116444736000000000) // 10000000) # 獲取原因 reason = struct.unpack_from("I", data, offset + 40)[0] # 獲取文件引用號(hào) file_ref = struct.unpack_from("Q", data, offset + 8)[0] records.append({ 'filename': filename, 'filepath': self.get_file_path(file_ref), 'time': time, 'reason': reason }) offset += record_length return records def get_file_path(self, file_ref): """獲取文件路徑""" try: return "Unknown Path" # 實(shí)際實(shí)現(xiàn)需要更復(fù)雜的邏輯 except: return "Unknown Path" def treeview_sort_column(self, col, reverse): """排序表格列""" l = [(self.tree.set(k, col), k) for k in self.tree.get_children('')] l.sort(reverse=reverse) # 重新排列項(xiàng)目 for index, (val, k) in enumerate(l): self.tree.move(k, '', index) # 切換排序方向 self.tree.heading(col, command=lambda: self.treeview_sort_column(col, not reverse)) def on_double_click(self, event): """雙擊打開文件或目錄""" item = self.tree.selection()[0] path = self.tree.item(item)['values'][3] try: os.startfile(path) except: pass def export_logs(self): """導(dǎo)出日志記錄""" try: with open('user_activities.csv', 'w', encoding='utf-8') as f: # 寫入表頭 f.write("操作時(shí)間,操作類型,操作對(duì)象,完整路徑\n") # 寫入數(shù)據(jù) for item in self.tree.get_children(): values = self.tree.item(item)['values'] f.write(f"{values[0]},{values[1]},{values[2]},{values[3]}\n") messagebox.showinfo("成功", "日志已導(dǎo)出到 user_activities.csv") except Exception as e: messagebox.showerror("錯(cuò)誤", f"導(dǎo)出日志失敗: {str(e)}") def clear_search(self): """清除搜索結(jié)果""" self.search_var.set("") self.tree.selection_remove(*self.tree.selection()) def delete_selected(self): """刪除選中的記錄""" selected = self.tree.selection() if not selected: messagebox.showwarning("警告", "請(qǐng)先選擇要?jiǎng)h除的記錄") return if messagebox.askyesno("確認(rèn)", "確定要?jiǎng)h除選中的記錄嗎?"): for item in selected: self.tree.delete(item) def search_logs(self): """搜索日志""" search_text = self.search_var.get().lower() if not search_text: return for item in self.tree.get_children(): values = self.tree.item(item)['values'] if any(search_text in str(value).lower() for value in values): self.tree.selection_add(item) else: self.tree.selection_remove(item) def show_context_menu(self, event): """顯示右鍵菜單""" menu = tk.Menu(self.root, tearoff=0) menu.add_command(label="復(fù)制", command=self.copy_selected) menu.add_command(label="刪除", command=self.delete_selected) menu.post(event.x_root, event.y_root) def copy_selected(self): """復(fù)制選中的記錄到剪貼板""" selected = self.tree.selection() if not selected: return text = "" for item in selected: values = self.tree.item(item)['values'] text += "\t".join(str(v) for v in values) + "\n" self.root.clipboard_clear() self.root.clipboard_append(text) def enable_usn_journal(self, drive): """啟用USN日志""" try: handle = win32file.CreateFile( f"\\\\.\\{drive}", win32con.GENERIC_READ | win32con.GENERIC_WRITE, win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE, None, win32con.OPEN_EXISTING, 0, None ) if handle == win32file.INVALID_HANDLE_VALUE: return False try: # 啟用USN日志 win32file.DeviceIoControl( handle, win32file.FSCTL_CREATE_USN_JOURNAL, struct.pack("QQ", 0, 0), None, None ) return True finally: win32file.CloseHandle(handle) except: return False def main(): # 如果不是管理員權(quán)限,則請(qǐng)求提升權(quán)限 if not is_admin(): ctypes.windll.shell32.ShellExecuteW( None, "runas", sys.executable, " ".join(sys.argv), None, 1) sys.exit() root = tk.Tk() app = PCActivityMonitor(root) root.mainloop() if __name__ == "__main__": main()
到此這篇關(guān)于基于Python編寫windows電腦用戶操作記錄查看器的文章就介紹到這了,更多相關(guān)Python電腦操作記錄查看器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Python中的偏函數(shù)(Partial Functions)
Python中的偏函數(shù)是來(lái)自函數(shù)式編程的一個(gè)強(qiáng)大工具,它的主要目標(biāo)是減少函數(shù)調(diào)用的復(fù)雜性這個(gè)概念可能起初看起來(lái)有點(diǎn)困難理解,但一旦你明白了它的工作方式,它可能會(huì)成為你的編程工具箱中的重要組成部分,文中有相關(guān)的代碼介紹,需要的朋友可以參考下2023-06-06python獲取引用對(duì)象的個(gè)數(shù)方式
今天小編就為大家分享一篇python獲取引用對(duì)象的個(gè)數(shù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2019-12-12Scrapy基于Python構(gòu)建強(qiáng)大網(wǎng)絡(luò)爬蟲框架實(shí)例探究
這篇文章主要為大家介紹了Scrapy基于Python構(gòu)建強(qiáng)大網(wǎng)絡(luò)爬蟲框架實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01PyCharm+PySpark遠(yuǎn)程調(diào)試的環(huán)境配置的方法
今天小編就為大家分享一篇PyCharm+PySpark遠(yuǎn)程調(diào)試的環(huán)境配置的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2018-11-11高效測(cè)試用例組織算法pairwise之Python實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇高效測(cè)試用例組織算法pairwise之Python實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-07-07