Skip to content

Instantly share code, notes, and snippets.

@roflsunriz
Last active February 5, 2025 13:01
Show Gist options
  • Save roflsunriz/771e8881c4b7a7ff4119a6d7cfdf6d87 to your computer and use it in GitHub Desktop.
Save roflsunriz/771e8881c4b7a7ff4119a6d7cfdf6d87 to your computer and use it in GitHub Desktop.
taskkill_wizard_gui : タスクキルをGUIで出来るウィザード
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import psutil
import os
import win32gui
import win32process
import win32con
import win32api
from PIL import Image, ImageTk
import io
import subprocess
import sys
import pkg_resources
import win32ui
from itertools import groupby
# グローバル変数を追加
MISSING_PACKAGES = None
def install_required_packages():
global MISSING_PACKAGES
required = {'psutil', 'pywin32', 'pillow'}
installed = {pkg.key.lower() for pkg in pkg_resources.working_set}
MISSING_PACKAGES = required - installed
if MISSING_PACKAGES:
# 確認ダイアログを表示
root = tk.Tk()
root.withdraw()
answer = messagebox.askyesno(
"パッケージインストール",
f"次のパッケージをインストールしますか?\n{', '.join(MISSING_PACKAGES)}\n(管理者権限が必要な場合があるのじゃ)",
parent=root
)
root.destroy()
if answer:
print("必要なパッケージをインストールするのじゃ!")
for package in MISSING_PACKAGES:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package])
MISSING_PACKAGES = None # インストール完了フラグ
class TaskKillWizard:
def __init__(self, root):
self.root = root
self.root.title("タスクキル ウィザード")
self.root.geometry("910x600") # ウィンドウの初期サイズを設定
self.exclude_processes = [
"System", "smss.exe", "csrss.exe", "wininit.exe", "services.exe",
"lsass.exe", "winlogon.exe", "svchost.exe", "taskhostw.exe", "explorer.exe",
"RuntimeBroker.exe", "SearchUI.exe", "ShellExperienceHost.exe", "TextInputHost.exe",
"StartMenuExperienceHost.exe", "ApplicationFrameHost.exe", "LockApp.exe", "SystemSettings.exe",
"SettingSyncHost.exe", "SecurityHealthService.exe", "spoolsv.exe", "fontdrvhost.exe",
"ctfmon.exe", "dwm.exe", "audiodg.exe", "taskeng.exe", "wlanext.exe", "WUDFHost.exe",
"conhost.exe", "sihost.exe", "dllhost.exe", "WerFault.exe", "smartscreen.exe",
"backgroundTaskHost.exe", "unsecapp.exe", "msdtc.exe", "msiexec.exe", "InstallService.exe",
"TrustedInstaller.exe", "MoNotificationUx.exe", "MoNotificationUxBroker.exe", "WmiPrvSE.exe",
"SearchProtocolHost.exe", "SearchIndexer.exe", "CompatTelRunner.exe", "OneDrive.exe",
"YourPhone.exe", "Microsoft.Photos.exe", "Microsoft.Windows.Cortana.exe", "Microsoft.WindowsStore.exe",
"Video.UI.exe", "GameBar.exe", "GameBarElevatedFT.exe", "TextInputHost.exe",
"WindowsInternal.ComposableShell.Experiences.TextInput.InputApp.exe",
"WindowsInternal.Shell.Broker.exe", "WindowsInternal.Shell.Experiences.exe",
"WindowsInternal.Shell.ShellExperienceHost.exe", "WindowsInternal.Shell.StartMenuExperienceHost.exe",
"WindowsInternal.Shell.SearchApp.exe", "WindowsInternal.Shell.SearchHost.exe",
"WindowsInternal.Shell.SearchIndexer.exe", "WindowsInternal.Shell.SearchProtocolHost.exe",
"WindowsInternal.Shell.SearchUI.exe", "WindowsInternal.Shell.SystemSettings.exe",
"WindowsInternal.Shell.SettingSyncHost.exe", "WindowsInternal.Shell.LockApp.exe",
"WindowsInternal.Shell.ApplicationFrameHost.exe", "WindowsInternal.Shell.RuntimeBroker.exe",
"WindowsInternal.Shell.SecurityHealthService.exe", "WindowsInternal.Shell.MoNotificationUx.exe",
"WindowsInternal.Shell.MoNotificationUxBroker.exe", "WindowsInternal.Shell.CompatTelRunner.exe",
"WindowsInternal.Shell.OneDrive.exe", "WindowsInternal.Shell.YourPhone.exe",
"WindowsInternal.Shell.Microsoft.Photos.exe", "WindowsInternal.Shell.Microsoft.Windows.Cortana.exe",
"WindowsInternal.Shell.Microsoft.WindowsStore.exe", "WindowsInternal.Shell.Video.UI.exe",
"WindowsInternal.Shell.GameBar.exe", "WindowsInternal.Shell.GameBarElevatedFT.exe"
]
# メインフレームを2段に分割
self.main_frame = ttk.Frame(root)
self.main_frame.pack(expand=True, fill='both', padx=5, pady=5)
# 上段:プロセスグリッド用フレーム
self.grid_container = ttk.Frame(self.main_frame)
self.grid_container.pack(expand=True, fill='both')
# スクロール可能なキャンバス
self.canvas = tk.Canvas(self.grid_container)
self.scrollbar_y = ttk.Scrollbar(self.grid_container, orient="vertical", command=self.canvas.yview)
self.scrollbar_x = ttk.Scrollbar(self.grid_container, orient="horizontal", command=self.canvas.xview)
# プロセス表示用フレーム
self.process_frame = ttk.Frame(self.canvas)
# スクロールバーの設定
self.canvas.configure(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set)
# レイアウトの配置
self.scrollbar_y.pack(side='right', fill='y')
self.scrollbar_x.pack(side='bottom', fill='x')
self.canvas.pack(side='left', fill='both', expand=True)
# キャンバスにプロセスフレームを配置
self.canvas_window = self.canvas.create_window((0, 0), window=self.process_frame, anchor='nw')
# 下段:ログエリア
self.log_area = scrolledtext.ScrolledText(self.main_frame, height=8)
self.log_area.pack(fill='x', padx=5, pady=5)
# 検索フレームを追加(グリッドの上部に)
self.search_frame = ttk.Frame(self.main_frame)
self.search_frame.pack(fill='x', padx=5, pady=5, before=self.grid_container)
# 検索エントリー
self.search_var = tk.StringVar()
self.search_var.trace('w', self.filter_processes)
self.search_entry = ttk.Entry(self.search_frame, textvariable=self.search_var)
self.search_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))
# クリアボタン
self.clear_btn = ttk.Button(self.search_frame, text="クリア", command=self.clear_search)
self.clear_btn.pack(side='right')
# プロセス情報を保持するリスト
self.process_frames = []
self.load_processes()
# スクロール関連のバインドを追加
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
self.process_frame.bind('<Configure>', self._on_frame_configure)
self.canvas.bind('<Configure>', self._on_canvas_configure)
def get_process_icon(self, process):
try:
exe_path = process.exe()
if not exe_path:
return self.get_default_icon()
# アイコンを抽出(まずExtractIconExを試す)
ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
try:
large, small = win32gui.ExtractIconEx(exe_path, 0)
if small:
icon_handle = small[0]
else:
# ExtractIconExが失敗した場合、SHGetFileInfoを試す
shinfo = win32gui.SHFILEINFO()
flags = win32con.SHGFI_ICON | win32con.SHGFI_SMALLICON
if win32gui.SHGetFileInfo(exe_path, 0, shinfo, len(shinfo), flags):
icon_handle = shinfo.hIcon
else:
return self.get_default_icon()
# アイコンをビットマップに変換
hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
hbmp = win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc, ico_x, ico_y)
hdc_mem = hdc.CreateCompatibleDC()
hdc_mem.SelectObject(hbmp)
# アイコンを描画
win32gui.DrawIconEx(
hdc_mem.GetHandleOutput(), 0, 0, icon_handle,
ico_x, ico_y, 0, None, win32con.DI_NORMAL
)
# ビットマップ情報を取得
bmpinfo = hbmp.GetInfo()
bmpstr = hbmp.GetBitmapBits(True)
# リソースを解放
win32gui.DestroyIcon(icon_handle)
if large: win32gui.DestroyIcon(large[0])
hdc_mem.DeleteDC()
hdc.DeleteDC()
win32gui.DeleteObject(hbmp.GetHandle())
# PILイメージに変換
img = Image.frombuffer(
'RGBA',
(ico_x, ico_y),
bmpstr, 'raw', 'BGRA', 0, 1
)
return ImageTk.PhotoImage(img)
except Exception:
return self.get_default_icon()
except Exception:
return self.get_default_icon()
def get_default_icon(self):
try:
shinfo = win32gui.SHFILEINFO()
flags = win32con.SHGFI_ICON | win32con.SHGFI_SMALLICON | win32con.SHGFI_USEFILEATTRIBUTES
if win32gui.SHGetFileInfo("dummy.exe", win32con.FILE_ATTRIBUTE_NORMAL, shinfo, len(shinfo), flags):
ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
hbmp = win32ui.CreateBitmap()
hbmp.CreateCompatibleBitmap(hdc, ico_x, ico_y)
hdc_mem = hdc.CreateCompatibleDC()
hdc_mem.SelectObject(hbmp)
win32gui.DrawIconEx(
hdc_mem.GetHandleOutput(), 0, 0, shinfo.hIcon,
ico_x, ico_y, 0, None, win32con.DI_NORMAL
)
bmpinfo = hbmp.GetInfo()
bmpstr = hbmp.GetBitmapBits(True)
win32gui.DestroyIcon(shinfo.hIcon)
hdc_mem.DeleteDC()
hdc.DeleteDC()
win32gui.DeleteObject(hbmp.GetHandle())
img = Image.frombuffer(
'RGBA',
(ico_x, ico_y),
bmpstr, 'raw', 'BGRA', 0, 1
)
return ImageTk.PhotoImage(img)
except:
pass
# 完全に失敗した場合は空の16x16画像を返す
return ImageTk.PhotoImage(Image.new('RGBA', (16, 16), (200, 200, 200, 255)))
def kill_process(self, name, pids):
success = 0
failed = 0
for pid in pids:
try:
process = psutil.Process(pid)
# 子プロセスも含めて終了
for child in process.children(recursive=True):
try:
child.terminate()
except:
try:
child.kill()
except:
pass
# メインプロセスの終了
try:
process.terminate() # まずは通常終了を試みる
process.wait(timeout=3) # 3秒待機
except psutil.TimeoutExpired:
try:
process.kill() # 強制終了
except:
failed += 1
self.log(f"PID {pid} の終了に失敗: 強制終了できませんでした")
continue
except:
failed += 1
self.log(f"PID {pid} の終了に失敗: プロセスにアクセスできません")
continue
success += 1
except psutil.NoSuchProcess:
self.log(f"PID {pid} は既に終了しています")
except psutil.AccessDenied:
failed += 1
self.log(f"PID {pid} の終了に失敗: アクセス権限がありません")
except Exception as e:
failed += 1
self.log(f"PID {pid} の終了に失敗: {str(e)}")
if success > 0:
self.log(f"プロセス {name} の {success} 個のインスタンスを終了したのじゃ!")
if failed > 0:
self.log(f"プロセス {name} の {failed} 個のインスタンスの終了に失敗したのじゃ...")
def log(self, message):
self.log_area.insert(tk.END, message + "\n")
self.log_area.see(tk.END)
def clear_search(self):
self.search_var.set("")
def filter_processes(self, *args):
search_text = self.search_var.get().lower()
for frame, name in self.process_frames:
if search_text in name.lower():
frame.grid()
else:
frame.grid_remove()
def load_processes(self):
# プロセス情報を収集してグループ化
processes = []
for process in psutil.process_iter(['pid', 'name', 'exe']):
if process.name() not in self.exclude_processes:
try:
processes.append(process)
except:
continue
# プロセス名でソート(大文字小文字を区別しない)
processes.sort(key=lambda x: x.name().lower())
# プロセス名でグループ化
grouped_processes = {}
for process in processes:
name = process.name()
if name not in grouped_processes:
grouped_processes[name] = []
grouped_processes[name].append(process.pid)
# グリッドにプロセスを配置
row = 0
col = 0
max_cols = 5
for name, pids in grouped_processes.items():
try:
frame = ttk.Frame(self.process_frame)
frame.grid(row=row, column=col, padx=5, pady=5, sticky='nsew')
# プロセスの代表からアイコンを取得
process = psutil.Process(pids[0])
icon = self.get_process_icon(process)
if not icon:
icon = tk.PhotoImage(width=32, height=32)
# インスタンス数を表示
instances_text = f"({len(pids)}個)" if len(pids) > 1 else ""
btn = ttk.Button(frame,
text=f"{name}\n{instances_text}",
image=icon,
compound='top',
command=lambda n=name, p=pids: self.kill_process(n, p))
btn.image = icon
btn.pack(padx=2, pady=2)
# フレームとプロセス名を保存(検索用)
self.process_frames.append((frame, name))
col += 1
if col >= max_cols:
col = 0
row += 1
except:
continue
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
def _on_frame_configure(self, event=None):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def _on_canvas_configure(self, event):
width = event.width
self.canvas.itemconfig(self.canvas_window, width=width)
def main():
# パッケージチェックのみ実行(実際のインストールはユーザー確認後)
install_required_packages()
root = tk.Tk()
app = TaskKillWizard(root)
root.mainloop()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment