Изучение работы Active Setup и получение его компонентов

Знаю, что Active Setup используется (1, 2) для выполнения команд и он хранится в реестре.

Помогите разобраться в теме подробнее и научиться вытаскивать компоненты Active Setup

Для работы с реестром использую winreg и функции, из прошлых наработок, для получения значений из реестра:

import os
import winreg

from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Union, Any
from winreg import QueryInfoKey, EnumKey, EnumValue, OpenKey, HKEYType


@dataclass
class Entry:
    name: str
    value: Any
    type: int


def expand_registry_key(key: str) -> str:
    return {
        'HKCU': 'HKEY_CURRENT_USER',
        'HKLM': 'HKEY_LOCAL_MACHINE',
    }.get(key, key)


def get_key(path: str) -> Optional[HKEYType]:
    # Example:
    #     path = r"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
    #     registry_key_name = "HKEY_LOCAL_MACHINE"
    #     relative_path = r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
    registry_key_name, relative_path = path.split('\\', maxsplit=1)
    registry_key_name = expand_registry_key(registry_key_name)

    registry_key = getattr(winreg, registry_key_name)

    try:
        return OpenKey(registry_key, relative_path)
    except:
        return


def get_subkeys(path: str) -> List[Tuple[str, HKEYType]]:
    items = []

    key = get_key(path)
    if not key:
        return items

    number_of_subkeys, _, _ = QueryInfoKey(key)
    for i in range(number_of_subkeys):
        sub_key_name = EnumKey(key, i)
        sub_key = OpenKey(key, sub_key_name)

        items.append((sub_key_name, sub_key))

    return items


def get_entries_as_dict(path: Union[str, HKEYType], raw_value=False, expand_vars=True) -> Dict[str, Union[Entry, Any]]:
    return {
        entry.name: entry.value if raw_value else entry
        for entry in get_entries(path, expand_vars)
    }


def get_entries(path: Union[str, HKEYType], expand_vars=True) -> List[Entry]:
    items = []

    if isinstance(path, HKEYType):
        key = path
    else:
        key = get_key(path)

    if not key:
        return items

    _, number_of_values, _ = QueryInfoKey(key)
    for i in range(number_of_values):
        name, value, type_value = EnumValue(key, i)

        if expand_vars and isinstance(value, str):
            value = os.path.expandvars(value)

        items.append(
            Entry(name, value, type_value)
        )

    return items

Например, так можно получить все ключи по указанному пути, после по первому ключу получить параметры:

subkeys = get_subkeys(r'HKLM\Software\Microsoft\Active Setup\Installed Components')
_, sub_key = subkeys[0]
print(get_entries_as_dict(sub_key, raw_value=True))
# {'': 'Microsoft Windows Media Player', 'ComponentID': 'WMPACCESS', 'DontAsk': 2, 'Enabled': 1, 'IsInstalled': 0, 'Locale': '*', 'LocalizedName': '@C:\\Windows\\system32\\wmploc.dll,-128', 'StubPath': 'C:\\Windows\\system32\\unregmp2.exe /ShowWMP', 'Version': '12,0,10011,16384'}

Эта же запись в реестре (regedit):

введите сюда описание изображения


Ответы (1 шт):

Автор решения: gil9red

Active Setup это команды, что запускаются один раз при LOGON. Операционная система смотрит в компоненты ветки HKEY_LOCAL_MACHINE (HKLM) и если есть отличия от компонентов в ветки HKEY_CURRENT_USER (HKCU), то происходит обновление компонентов в HKCU и выполнение команд.

Пути реестра:

  • HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\Installed Components
  • HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Active Setup\Installed Components
  • HKEY_CURRENT_USER\Software\Microsoft\Active Setup\Installed Components
  • HKEY_CURRENT_USER\Software\Wow6432Node\Microsoft\Active Setup\Installed Components

Описание полей:

Поле Комментарий
GUID Каждый компонент имеет уникальную строку в названии ключа. В ключе не обязательно должен быть GUID, просто он позволяет упростить условие уникальности
Default Value Пустое имя параметра. Опциональное имя компоненты
IsInstalled По-умолчанию, 1. Возможные значения: 0 - команда компоненты не будет выполнена, 1 - команда будет выполнена.
Locale Произвольная строка, нет проверки ее формата, поэтому может быть что угодно написано, а не например значения "ru", "en". Если это поле в HKLM отличается от компоненты в HKCU (или ее нет), то команда запускается
StubPath Команда, что будет выполнена. Может быть путем к файлу или командной строкой
Version Формат: 4 числа, разделенных запятыми, пример: 1,2,3,4. Определяет будет ли запуск компоненты: если версия в HKCU отсутствует или меньше версии в HKLM

Есть и другие поля, но по ним не нашел описания и есть подозрение, что они не задокументированы. Например, встречался параметр "Localized Name" только для "Microsoft Edge" и "LocalizedName" для остальных компонентов. Поэтому, поля, что не входят в список полей Component будут в поле other_fields (см. в реализации).

Реализация:

Действия:

  • Функции из вопрос перенес в common.py
  • Добавил класс Component и описал поля в соответствии с таблицей выше
  • Сделал функцию get_active_setup_components, что перебирает пути Active Setup и собирает список из Component

Пример:

from dataclasses import dataclass, field
from typing import Dict, Any, List

from common import get_subkeys, get_entries_as_dict


PATHS = [
    r'HKLM\Software\Microsoft\Active Setup\Installed Components',
    r'HKLM\Software\Wow6432Node\Microsoft\Active Setup\Installed Components',
    r'HKCU\Software\Microsoft\Active Setup\Installed Components',
    r'HKCU\Software\Wow6432Node\Microsoft\Active Setup\Installed Components',
]


@dataclass
class Component:
    guid: str
    default: str = ''
    is_installed: bool = True
    locale: str = ''
    stub_path: str = ''
    version: str = ''
    other_fields: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def create(cls, guid: str, fields: Dict[str, Any]) -> 'Component':
        return cls(
            guid=guid,
            default=fields.pop('', ''),
            is_installed=fields.pop('IsInstalled', True),
            locale=fields.pop('Locale', ''),
            stub_path=fields.pop('StubPath', ''),
            version=fields.pop('Version', ''),
            other_fields=fields,
        )


def get_active_setup_components(exists_stub_path=True) -> Dict[str, List[Component]]:
    path_by_items = dict()

    for path in PATHS:
        if path not in path_by_items:
            path_by_items[path] = []

        for sub_key_name, sub_key in get_subkeys(path):
            entries = get_entries_as_dict(sub_key, raw_value=True)
            component = Component.create(sub_key_name, entries)

            # Если задана проверка наличия stub_path и он пустой
            if exists_stub_path and not component.stub_path.strip():
                continue

            path_by_items[path].append(component)

    return path_by_items

Пример использования функции:

path_by_components = get_active_setup_components()
for path, components in path_by_components.items():
    print(f'{path} ({len(components)}):')
    for component in components:
        print(f'    {component}')

    print()
→ Ссылка