DEV Community

vast cow
vast cow

Posted on

How to Use a Tool to Remove a Specific App from Windows “File Associations”

On Windows, uninstalled applications or portable applications can sometimes leave behind only their file-association information in the registry.

For example, when you display “Open with” for files such as .epub or .pdf, an application you no longer use may remain listed as an option.

This section explains how to use a Python tool that examines file associations registered under HKEY_CLASSES_ROOT and deletes registry keys associated with a specified executable file.

What This Tool Does

This tool examines the keys directly under HKEY_CLASSES_ROOT, abbreviated as HKCR.

For each key, it checks:

HKEY_CLASSES_ROOT\<key name>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

and retrieves the executable filename from the command set as the default value.

For example, suppose the following entry exists:

"C:\Program Files\Calibre2\ebook-viewer.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

If the specified executable filename is ebook-viewer.exe, this association is detected as a target.

When run normally, the tool deletes the target registry key. If you add --dry-run, it only displays the targets without deleting them.

Requirements

This script is for Windows only.

It uses only the following Python standard-library modules:

  • argparse
  • ctypes
  • os
  • winreg

Therefore, no additional packages need to be installed.

It can be used on a Windows system with Python 3 installed.

First, Save the Script

Save the code under a filename such as:

remove_association.py
Enter fullscreen mode Exit fullscreen mode

Open Command Prompt or PowerShell and move to the directory where you saved the file.

Example:

cd C:\Tools
Enter fullscreen mode Exit fullscreen mode

Always Check with --dry-run First

Because this tool deletes registry keys, it is safer not to delete anything immediately. First, use --dry-run to review the targets.

The syntax is:

python remove_association.py <executable filename> --dry-run
Enter fullscreen mode Exit fullscreen mode

For example, to check entries associated with calibre.exe, run:

python remove_association.py calibre.exe --dry-run
Enter fullscreen mode Exit fullscreen mode

If targets are found, the output will look like this:

Found 2 association(s).

HKEY_CLASSES_ROOT\Calibre...
  command = "C:\Program Files\Calibre2\calibre.exe" "%1"

HKEY_CLASSES_ROOT\...
  command = "C:\Program Files\Calibre2\calibre.exe" "%1"

dry-run: No keys were deleted.
Enter fullscreen mode Exit fullscreen mode

If the final line says:

dry-run: No keys were deleted.
Enter fullscreen mode Exit fullscreen mode

then the registry has not been modified.

Actually Deleting the Entries

After reviewing the --dry-run results and confirming that only unnecessary associations were detected, run the command again without --dry-run.

python remove_association.py calibre.exe
Enter fullscreen mode Exit fullscreen mode

If deletion succeeds, the tool displays:

DELETED: HKEY_CLASSES_ROOT\...
Enter fullscreen mode Exit fullscreen mode

If multiple associations are found, the detected keys are deleted one by one.

Specify Only the Executable Filename

As a general rule, specify only the executable filename as the argument, not the full path.

For example, instead of:

C:\Program Files\Calibre2\calibre.exe
Enter fullscreen mode Exit fullscreen mode

specify:

calibre.exe
Enter fullscreen mode Exit fullscreen mode

The tool parses the command registered in the registry according to Windows command-line rules, extracts only the filename of the executable at the beginning of the command, and compares that filename.

The comparison is case-insensitive. Therefore:

CALIBRE.EXE
Enter fullscreen mode Exit fullscreen mode

and:

calibre.exe
Enter fullscreen mode Exit fullscreen mode

are treated as the same filename.

Why It Does Not Use a Simple String Search

An association command does not necessarily contain only the executable path.

For example, it may look like:

"C:\Program Files\Example\viewer.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

or:

"C:\Program Files\Example\viewer.exe" --open "%1"
Enter fullscreen mode Exit fullscreen mode

In addition, Windows command lines have their own rules for handling quotation marks and spaces.

For this reason, the tool parses the command string using the Windows API function:

CommandLineToArgvW
Enter fullscreen mode Exit fullscreen mode

It then extracts the executable filename from the first parsed argument using:

os.path.basename(parts[0])
Enter fullscreen mode Exit fullscreen mode

This structure is less prone to false positives than a simple substring check such as:

if "calibre.exe" in command:
Enter fullscreen mode Exit fullscreen mode

Support for REG_EXPAND_SZ

The tool also supports commands stored not only as ordinary REG_SZ strings, but also as REG_EXPAND_SZ values containing environment variables.

For example:

"%ProgramFiles%\Example\viewer.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

In this case, the tool expands the environment variables using:

os.path.expandvars(value)
Enter fullscreen mode Exit fullscreen mode

before parsing the command.

How Associations Are Searched

The core of the search process is find_associations().

def find_associations(target_exe: str):
Enter fullscreen mode Exit fullscreen mode

This function sequentially enumerates the keys directly under HKEY_CLASSES_ROOT using winreg.EnumKey().

For each key, it reads:

<key>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

and, if the executable filename matches the specified name, returns it as a deletion candidate with:

yield key_name, command
Enter fullscreen mode Exit fullscreen mode

In other words, this tool searches the shell\open\command entry under each key directly beneath HKCR.

Note that it is not a tool that comprehensively searches every type of file-association information that may exist in Windows.

Deletion Includes Subkeys

A registry key cannot be deleted directly if it contains subkeys.

For that reason, delete_registry_tree() recursively deletes child keys before deleting the parent key.

Conceptually, if the structure looks like this:

Target key
├─ DefaultIcon
├─ shell
│  └─ open
│     └─ command
└─ other entries
Enter fullscreen mode Exit fullscreen mode

the tool deletes entries from the bottom of the hierarchy upward, then finally deletes the “Target key” itself.

Therefore, when this tool deletes a detected key, it removes not only shell\open\command, but the entire association key.

This is an important point.

If You Get a Permission Error

During deletion, you may see:

ACCESS DENIED: HKEY_CLASSES_ROOT\...
Enter fullscreen mode Exit fullscreen mode

This can occur when the current user does not have permission to modify the registry key.

If necessary, run the command from PowerShell or Command Prompt opened with “Run as administrator.”

However, a permission error does not mean you should automatically delete the key with administrator privileges.

First, review the --dry-run output and confirm that the key is truly unnecessary.

If No Target Is Found

If no matching association exists, the tool displays a message such as:

No associations matching 'calibre.exe' were found.
Enter fullscreen mode Exit fullscreen mode

In this case, nothing is deleted.

However, there may also be cases where the association is visible in Windows but is not detected by this tool.

Windows stores file-association information in multiple locations, and this tool checks only registrations in the following form:

HKEY_CLASSES_ROOT\<key>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

Usage Example

To only check associations linked to foo.exe, run:

python remove_association.py foo.exe --dry-run
Enter fullscreen mode Exit fullscreen mode

After reviewing the results, to delete them, run:

python remove_association.py foo.exe
Enter fullscreen mode Exit fullscreen mode

For example, suppose the detected result is:

Found 1 association(s).

HKEY_CLASSES_ROOT\Foo.Document
  command = "C:\OldApps\Foo\foo.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

If you run the tool normally in this state, the deletion target is not simply:

HKEY_CLASSES_ROOT\Foo.Document\shell\open\command
Enter fullscreen mode Exit fullscreen mode

Instead, the entire tree under:

HKEY_CLASSES_ROOT\Foo.Document
Enter fullscreen mode Exit fullscreen mode

is deleted.

Therefore, if Foo.Document contains other registered information, that information will also be lost.

Precautions

This tool directly deletes registry keys. It does not provide a function to restore deleted keys.

One especially important point is that when it finds a shell\open\command matching the specified EXE name, it recursively deletes not just that command key, but the association key itself directly under HKCR.

For that reason, the following workflow is recommended:

  1. Confirm the target EXE filename.
  2. Search using --dry-run.
  3. Review every registry key that is displayed.
  4. If necessary, export the target key from Registry Editor to create a backup.
  5. Only after confirming there is no problem, run the tool normally.

It is particularly advisable to avoid targeting built-in Windows applications or applications that are currently in use.

Summary

This tool examines Windows HKEY_CLASSES_ROOT, searches for associations where the specified executable is registered under:

<key>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

and deletes those keys.

The basic usage is simple.

To only check:

python remove_association.py calibre.exe --dry-run
Enter fullscreen mode Exit fullscreen mode

To actually delete:

python remove_association.py calibre.exe
Enter fullscreen mode Exit fullscreen mode

Because the tool directly modifies the registry, the most important step is to review the targets with --dry-run first.

It can be useful for cleaning up old file associations left behind after uninstalling applications or associations for applications you no longer need, but because it deletes the entire association key, review the contents carefully and use it with caution.

import argparse
import ctypes
import os
import winreg
from ctypes import wintypes


shell32 = ctypes.WinDLL("shell32", use_last_error=True)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)

shell32.CommandLineToArgvW.argtypes = [
    wintypes.LPCWSTR,
    ctypes.POINTER(ctypes.c_int),
]
shell32.CommandLineToArgvW.restype = ctypes.POINTER(wintypes.LPWSTR)

kernel32.LocalFree.argtypes = [wintypes.HLOCAL]
kernel32.LocalFree.restype = wintypes.HLOCAL


def split_windows_command(command: str) -> list[str]:
    argc = ctypes.c_int()

    argv = shell32.CommandLineToArgvW(command, ctypes.byref(argc))
    if not argv:
        raise ctypes.WinError(ctypes.get_last_error())

    parts = [argv[i] for i in range(argc.value)]
    kernel32.LocalFree(argv)

    return parts


def get_open_command(root, key_name: str) -> str | None:
    subkey = rf"{key_name}\shell\open\command"

    try:
        with winreg.OpenKey(root, subkey, 0, winreg.KEY_READ) as key:
            value, value_type = winreg.QueryValueEx(key, "")

            if value_type in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
                if value_type == winreg.REG_EXPAND_SZ:
                    value = os.path.expandvars(value)
                return value

    except (FileNotFoundError, PermissionError, OSError):
        pass

    return None


def executable_name_from_command(command: str) -> str | None:
    try:
        parts = split_windows_command(command)
    except (ValueError, OSError):
        return None

    if not parts:
        return None

    return os.path.basename(parts[0])


def find_associations(target_exe: str):
    target_exe = target_exe.casefold()

    root = winreg.HKEY_CLASSES_ROOT
    index = 0

    while True:
        try:
            key_name = winreg.EnumKey(root, index)
        except OSError:
            break

        index += 1

        command = get_open_command(root, key_name)
        if command is None:
            continue

        executable_name = executable_name_from_command(command)
        if executable_name is None:
            continue

        if executable_name.casefold() == target_exe:
            yield key_name, command


def delete_registry_tree(root, subkey: str):
    try:
        with winreg.OpenKey(
            root,
            subkey,
            0,
            winreg.KEY_READ | winreg.KEY_WRITE,
        ) as key:
            while True:
                try:
                    child = winreg.EnumKey(key, 0)
                except OSError:
                    break

                delete_registry_tree(root, rf"{subkey}\{child}")

        winreg.DeleteKey(root, subkey)

    except FileNotFoundError:
        pass


def main():
    parser = argparse.ArgumentParser(
        description=(
            r"Checks the executable name in HKCR\<key>\shell\open\command and "
            "deletes keys associated with the specified exe."
        )
    )

    parser.add_argument(
        "exe",
        help="Executable filename to search for. Example: calibre.exe",
    )

    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="List only the target keys without deleting them",
    )

    args = parser.parse_args()

    matches = list(find_associations(args.exe))

    if not matches:
        print(f"No associations matching {args.exe!r} were found.")
        return

    print(f"Found {len(matches)} association(s).")
    print()

    for key_name, command in matches:
        print(fr"HKEY_CLASSES_ROOT\{key_name}")
        print(f"  command = {command}")

    if args.dry_run:
        print()
        print("dry-run: No keys were deleted.")
        return

    print()
    print("Deleting.")

    for key_name, command in matches:
        full_name = fr"HKEY_CLASSES_ROOT\{key_name}"

        try:
            delete_registry_tree(winreg.HKEY_CLASSES_ROOT, key_name)
            print(f"DELETED: {full_name}")
        except PermissionError:
            print(f"ACCESS DENIED: {full_name}")
        except OSError as e:
            print(f"ERROR: {full_name}: {e}")


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Windowsの「関連付け」から特定アプリを削除するツールの使い方

Windowsでは、アンインストール済みのアプリやポータブル版のアプリが、ファイルの関連付け情報だけをレジストリに残してしまうことがあります。

たとえば .epub.pdf などのファイルで「プログラムから開く」を表示したとき、すでに使っていないアプリが候補として残るケースです。

ここでは、HKEY_CLASSES_ROOT に登録されているファイル関連付けを調べ、指定した実行ファイルに紐付いているレジストリキーを削除するPythonツールの使い方を説明します。

このツールがすること

このツールは HKEY_CLASSES_ROOT、略して HKCR の直下にあるキーを調べます。

各キーについて、

HKEY_CLASSES_ROOT\<キー名>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

を確認し、既定値に設定されているコマンドの実行ファイル名を取得します。

たとえば次のような登録があったとします。

"C:\Program Files\Calibre2\ebook-viewer.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

指定した実行ファイル名が ebook-viewer.exe なら、この関連付けを対象として検出します。

通常実行すると対象のレジストリキーを削除し、--dry-run を付けると削除せず対象だけを表示します。

動作環境

このスクリプトはWindows専用です。

Pythonの標準ライブラリである、

  • argparse
  • ctypes
  • os
  • winreg

だけを使用しているため、追加パッケージのインストールは必要ありません。

Python 3をインストールしたWindows環境で使用できます。

まずはスクリプトを保存する

コードを、たとえば次の名前で保存します。

remove_association.py
Enter fullscreen mode Exit fullscreen mode

コマンドプロンプトまたはPowerShellを開き、ファイルを保存したディレクトリへ移動してください。

例:

cd C:\Tools
Enter fullscreen mode Exit fullscreen mode

最初は必ず --dry-run で確認する

レジストリキーを削除するツールなので、最初から削除を実行するのではなく、まず --dry-run を使って対象を確認するのが安全です。

書式は次のとおりです。

python remove_association.py <実行ファイル名> --dry-run
Enter fullscreen mode Exit fullscreen mode

たとえば calibre.exe に関連付けられた項目を調べる場合は、

python remove_association.py calibre.exe --dry-run
Enter fullscreen mode Exit fullscreen mode

と実行します。

対象が見つかると、次のような形式で表示されます。

Found 2 association(s).

HKEY_CLASSES_ROOT\Calibre...
  command = "C:\Program Files\Calibre2\calibre.exe" "%1"

HKEY_CLASSES_ROOT\...
  command = "C:\Program Files\Calibre2\calibre.exe" "%1"

dry-run: No keys were deleted.
Enter fullscreen mode Exit fullscreen mode

最後に

dry-run: No keys were deleted.
Enter fullscreen mode Exit fullscreen mode

と表示されている場合、レジストリは変更されていません。

実際に削除する

--dry-run の結果を確認し、本当に不要な関連付けだけが検出されていることを確認したら、--dry-run を外して実行します。

python remove_association.py calibre.exe
Enter fullscreen mode Exit fullscreen mode

削除に成功すると、

DELETED: HKEY_CLASSES_ROOT\...
Enter fullscreen mode Exit fullscreen mode

と表示されます。

複数の関連付けが見つかった場合は、検出されたキーが順番に削除されます。

実行ファイル名だけを指定する

引数に指定するのは、基本的には実行ファイルのフルパスではなくファイル名です。

たとえば、

C:\Program Files\Calibre2\calibre.exe
Enter fullscreen mode Exit fullscreen mode

ではなく、

calibre.exe
Enter fullscreen mode Exit fullscreen mode

を指定します。

このツールはレジストリに登録されたコマンドをWindowsのルールに従って分解し、その先頭にある実行ファイルのファイル名だけを取り出して比較しています。

比較では大文字・小文字を区別しません。そのため、

CALIBRE.EXE
Enter fullscreen mode Exit fullscreen mode


calibre.exe
Enter fullscreen mode Exit fullscreen mode

は同じものとして扱われます。

なぜ単純な文字列検索をしていないのか

関連付けのコマンドは、単純に実行ファイルのパスだけが保存されているとは限りません。

たとえば、

"C:\Program Files\Example\viewer.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

や、

"C:\Program Files\Example\viewer.exe" --open "%1"
Enter fullscreen mode Exit fullscreen mode

のような形式があります。

さらにWindowsのコマンドラインでは、引用符や空白の扱いに独自の規則があります。

そこでこのツールでは、

CommandLineToArgvW
Enter fullscreen mode Exit fullscreen mode

というWindows APIを使ってコマンド文字列を解析しています。

解析後の最初の引数から、

os.path.basename(parts[0])
Enter fullscreen mode Exit fullscreen mode

で実行ファイル名を取り出しています。

そのため、単純に

if "calibre.exe" in command:
Enter fullscreen mode Exit fullscreen mode

のような部分一致で判定するより、誤検出が起こりにくい構造になっています。

REG_EXPAND_SZ にも対応

レジストリのコマンドが通常の文字列 REG_SZ だけでなく、環境変数を含む REG_EXPAND_SZ だった場合にも対応しています。

たとえば、

"%ProgramFiles%\Example\viewer.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

のような値です。

この場合は、

os.path.expandvars(value)
Enter fullscreen mode Exit fullscreen mode

によって環境変数を展開してから解析します。

関連付けの検索方法

検索の中心となっているのが find_associations() です。

def find_associations(target_exe: str):
Enter fullscreen mode Exit fullscreen mode

ここでは HKEY_CLASSES_ROOT の直下を winreg.EnumKey() で順番に走査しています。

それぞれについて、

<キー>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

を読み取り、実行ファイル名が指定された名前と一致すると、

yield key_name, command
Enter fullscreen mode Exit fullscreen mode

で削除候補として返します。

つまり、このツールの検索対象は HKCR直下の各キーに存在する shell\open\command です。

Windowsに存在するあらゆる種類の関連付け情報を網羅的に検索するツールではない点には注意してください。

削除はサブキーも含めて行われる

レジストリキーは、その下にサブキーが存在すると単純には削除できません。

そのため delete_registry_tree() では、子キーを再帰的に削除してから親キーを削除しています。

概念的には、

対象キー
├─ DefaultIcon
├─ shell
│  └─ open
│     └─ command
└─ その他
Enter fullscreen mode Exit fullscreen mode

という構造があった場合、下の階層から順番に削除し、最後に「対象キー」そのものを削除します。

したがって、このツールで検出されたキーを削除すると、shell\open\command だけではなく その関連付けキー全体が削除されます。

これは重要なポイントです。

権限エラーが出た場合

削除時に、

ACCESS DENIED: HKEY_CLASSES_ROOT\...
Enter fullscreen mode Exit fullscreen mode

と表示されることがあります。

これは、そのレジストリキーを書き換えるための権限が現在のユーザーにない場合などに発生します。

必要であれば「管理者として実行」したPowerShellやコマンドプロンプトから実行します。

ただし、権限エラーが出たからといって、無条件に管理者権限で削除すべきとは限りません。

まず --dry-run の出力を見て、そのキーが本当に不要なものか確認してください。

対象が見つからない場合

一致する関連付けが存在しない場合は、

No associations matching 'calibre.exe' were found.
Enter fullscreen mode Exit fullscreen mode

のように表示されます。

この場合、何も削除されません。

ただし、目的の関連付けがWindows上に表示されているにもかかわらず検出されないこともあります。

Windowsのファイル関連付けは複数の場所に保存されており、このツールが調べているのは、

HKEY_CLASSES_ROOT\<key>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

という形式の登録だけだからです。

使用例

foo.exe に紐付く関連付けを確認するだけなら、

python remove_association.py foo.exe --dry-run
Enter fullscreen mode Exit fullscreen mode

確認後、削除するなら、

python remove_association.py foo.exe
Enter fullscreen mode Exit fullscreen mode

です。

たとえば検出結果が、

Found 1 association(s).

HKEY_CLASSES_ROOT\Foo.Document
  command = "C:\OldApps\Foo\foo.exe" "%1"
Enter fullscreen mode Exit fullscreen mode

だったとします。

この状態で通常実行すると、削除対象は単に、

HKEY_CLASSES_ROOT\Foo.Document\shell\open\command
Enter fullscreen mode Exit fullscreen mode

だけではありません。

HKEY_CLASSES_ROOT\Foo.Document
Enter fullscreen mode Exit fullscreen mode

以下のツリー全体です。

そのため、Foo.Document に別の情報も登録されている場合、それらも失われます。

使用上の注意

このツールはレジストリを直接削除します。削除したキーを元に戻す機能はありません。

特に注意すべきなのは、指定したEXE名に一致した shell\open\command を見つけると、その command キーだけでなく HKCR直下の関連付けキーそのものを再帰的に削除する ことです。

そのため、基本的には次の順序で使うことを推奨します。

  1. 対象となるEXE名を確認する
  2. --dry-run で検索する
  3. 表示されたすべてのレジストリキーを確認する
  4. 必要ならレジストリエディターで対象キーをエクスポートしてバックアップする
  5. 問題がないことを確認してから通常実行する

特にWindows標準アプリや現在使用中のアプリを対象にするのは避けたほうがよいでしょう。

まとめ

このツールは、Windowsの HKEY_CLASSES_ROOT を調べて、

<キー>\shell\open\command
Enter fullscreen mode Exit fullscreen mode

に指定した実行ファイルが登録されている関連付けを探し、そのキーを削除するためのものです。

基本的な使い方はシンプルです。

確認だけなら、

python remove_association.py calibre.exe --dry-run
Enter fullscreen mode Exit fullscreen mode

実際に削除するなら、

python remove_association.py calibre.exe
Enter fullscreen mode Exit fullscreen mode

です。

レジストリを直接操作するため、最初に --dry-run で対象を確認することが最も重要です。

アンインストール後も残っている古いファイル関連付けや、不要になったアプリの関連付けを整理するときに使えるツールですが、削除対象は関連付けキー全体なので、内容を確認したうえで慎重に使用してください。

import argparse
import ctypes
import os
import winreg
from ctypes import wintypes


shell32 = ctypes.WinDLL("shell32", use_last_error=True)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)

shell32.CommandLineToArgvW.argtypes = [
    wintypes.LPCWSTR,
    ctypes.POINTER(ctypes.c_int),
]
shell32.CommandLineToArgvW.restype = ctypes.POINTER(wintypes.LPWSTR)

kernel32.LocalFree.argtypes = [wintypes.HLOCAL]
kernel32.LocalFree.restype = wintypes.HLOCAL


def split_windows_command(command: str) -> list[str]:
    argc = ctypes.c_int()

    argv = shell32.CommandLineToArgvW(command, ctypes.byref(argc))
    if not argv:
        raise ctypes.WinError(ctypes.get_last_error())

    parts = [argv[i] for i in range(argc.value)]
    kernel32.LocalFree(argv)

    return parts


def get_open_command(root, key_name: str) -> str | None:
    subkey = rf"{key_name}\shell\open\command"

    try:
        with winreg.OpenKey(root, subkey, 0, winreg.KEY_READ) as key:
            value, value_type = winreg.QueryValueEx(key, "")

            if value_type in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
                if value_type == winreg.REG_EXPAND_SZ:
                    value = os.path.expandvars(value)
                return value

    except (FileNotFoundError, PermissionError, OSError):
        pass

    return None


def executable_name_from_command(command: str) -> str | None:
    try:
        parts = split_windows_command(command)
    except (ValueError, OSError):
        return None

    if not parts:
        return None

    return os.path.basename(parts[0])


def find_associations(target_exe: str):
    target_exe = target_exe.casefold()

    root = winreg.HKEY_CLASSES_ROOT
    index = 0

    while True:
        try:
            key_name = winreg.EnumKey(root, index)
        except OSError:
            break

        index += 1

        command = get_open_command(root, key_name)
        if command is None:
            continue

        executable_name = executable_name_from_command(command)
        if executable_name is None:
            continue

        if executable_name.casefold() == target_exe:
            yield key_name, command


def delete_registry_tree(root, subkey: str):
    try:
        with winreg.OpenKey(
            root,
            subkey,
            0,
            winreg.KEY_READ | winreg.KEY_WRITE,
        ) as key:
            while True:
                try:
                    child = winreg.EnumKey(key, 0)
                except OSError:
                    break

                delete_registry_tree(root, rf"{subkey}\{child}")

        winreg.DeleteKey(root, subkey)

    except FileNotFoundError:
        pass


def main():
    parser = argparse.ArgumentParser(
        description=(
            r"Checks the executable name in HKCR\<key>\shell\open\command and "
            "deletes keys associated with the specified exe."
        )
    )

    parser.add_argument(
        "exe",
        help="Executable filename to search for. Example: calibre.exe",
    )

    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="List only the target keys without deleting them",
    )

    args = parser.parse_args()

    matches = list(find_associations(args.exe))

    if not matches:
        print(f"No associations matching {args.exe!r} were found.")
        return

    print(f"Found {len(matches)} association(s).")
    print()

    for key_name, command in matches:
        print(fr"HKEY_CLASSES_ROOT\{key_name}")
        print(f"  command = {command}")

    if args.dry_run:
        print()
        print("dry-run: No keys were deleted.")
        return

    print()
    print("Deleting.")

    for key_name, command in matches:
        full_name = fr"HKEY_CLASSES_ROOT\{key_name}"

        try:
            delete_registry_tree(winreg.HKEY_CLASSES_ROOT, key_name)
            print(f"DELETED: {full_name}")
        except PermissionError:
            print(f"ACCESS DENIED: {full_name}")
        except OSError as e:
            print(f"ERROR: {full_name}: {e}")


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Top comments (0)