Интеграция с оболочкой Windows сегодня ── контекстные меню, ассоциации файлов и что изменилось в Windows 11

· · Windows, Расширения оболочки, Контекстное меню, Ассоциация файлов, COM, Windows 11, Проводник, MSIX, Разработка для Windows

Ко мне обратились с «мы заменили ПК на Windows 11, и контекстное меню приложения, которое вы нам сделали много лет назад, исчезло». Послушав внимательнее, оно не исчезло. Щёлкните файл правой кнопкой, выберите «Показать дополнительные параметры» внизу меню — и знакомое меню появляется как всегда. Иными словами, пункты меню внутреннего приложения спрятали на один щелчок глубже. С мест говорят «лишний щелчок» и «стало больше обращений, что пункт не находят».

Это не сбой и не ошибка настройки, а изменение проекта Windows 11. Контекстное меню Проводника стало двухслойной структурой, старой и новой, а условия, чтобы пункт попал в новое меню, стали совершенно другими, чем раньше.

При этом механизм ассоциации файлов и расширений оболочки под ним по-прежнему старый мир COM и реестра. Ключ расширения указывает на ProgID, verb ProgID хранит командную строку, а более сложное расширение работает как внутрипроцессный COM-сервер (DLL), загружаемый в Проводник — эта структура не менялась больше двадцати лет. Если вы не знаете и неизменный фундамент, и меню, которое Windows 11 разделила надвое, вы не отличите «меню не появляется», «оно спрятано» и «оно появляется дважды».

Статья рассчитана на ИТ-сотрудников малых и средних компаний и на разработчиков Windows, которые сопровождают деловые приложения. Она связывает в одну картину трёхслойную структуру ассоциации файлов, оговорки классических расширений оболочки, как целиться в новое контекстное меню Windows 11, а также регистрацию, очистку и устранение неполадок установщика.

1. Сначала вывод

  • Фундамент контекстного меню и ассоциации файлов — трёхслойная структура реестра «ключ расширения → ProgID → verb». Ключ расширения — указатель на ProgID, ProgID — сущность, а shell\<verb>\command под ним хранит командную строку.1
  • HKEY_CLASSES_ROOT (HKCR) — не независимый куст, а объединённое представление HKLM\Software\Classes и HKCU\Software\Classes. Регистрацию для всех пользователей пишите в HKLM, для одного пользователя — в HKCU, а HKCR считайте только для чтения.2
  • Приложение по умолчанию (то, что открывается двойным щелчком) рассчитано на выбор пользователя, и программа не может его украсть. ОС защищает выбор пользователя; установщик может зарегистрироваться как кандидат.3
  • Классическое расширение оболочки — внутрипроцессная COM DLL, загружаемая в Проводник. Сбой или задержка расширения распространяются на Проводник целиком (и на другие приложения, использующие оболочку); в 64-разрядной среде нужна 64-разрядная DLL; реализация на управляемом коде не поддерживается.45
  • В Windows 11 контекстное меню разделилось надвое. В новом меню появляются только команды, зарегистрированные через IExplorerCommand плюс идентичность пакета; классические расширения IContextMenu перенесены в старое меню под «Показать дополнительные параметры» (Shift+F10).67
  • Официальный путь поместить свою команду в новое меню — зарегистрировать нативную DLL, реализующую IExplorerCommand, в манифесте MSIX (desktop4:FileExplorerContextMenus). Приложение, которое не может стать MSIX, может получить только идентичность через sparse package (MSIX с внешним расположением).78
  • Если нужно только «открыть этим приложением», ассоциации и статического verb по-прежнему достаточно. DLL расширения оболочки не нужна, и сама Microsoft прямо говорит «выберите самый простой метод, удовлетворяющий требованиям (статический verb)».9
  • После регистрации или изменения уведомите через SHChangeNotify(SHCNE_ASSOCCHANGED); при удалении удалите ProgID, но не удаляйте значение по умолчанию ключа расширения — такова официальная рекомендация. Интеграция с оболочкой включает проект очистки.110

Одним предложением: мир ассоциаций и verb не изменился; только показ меню в Windows 11 разделился надвое. Ниже идём от фундамента.

2. Как работает ассоциация файлов — трёхслойная структура ключ расширения → ProgID → verb

2.1. Читаем трёхслойную структуру на одном примере

Что происходит при двойном щелчке по файлу данного расширения, решают три слоя ключей реестра.1

HKEY_CLASSES_ROOT
   .kmrpt                                  ← (1) Ключ расширения
      (Default) = KomuraSoft.Report.1      ←     Указатель, который только именует ProgID
      OpenWithProgids
         KomuraSoft.Report.1               ←     Кандидат в «Открыть с помощью»
   KomuraSoft.Report.1                     ← (2) ProgID (сущность ассоциации)
      (Default) = Komura Report document
      DefaultIcon
         (Default) = "C:\Program Files\KomuraSoft\Report.exe",0
      shell                                ← (3) Список verb
         open
            command
               (Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
  • (1) Ключ расширения (.kmrpt) только указывает на имя ProgID как значение по умолчанию. Писать команду прямо сюда — ошибка.
  • (2) ProgID (KomuraSoft.Report.1) — сущность ассоциации; в нём отображаемое имя, значок и список verb.
  • (3) Verb — действие вроде «открыть» или «печать», а значение по умолчанию shell\open\command — реально запускаемая командная строка.

Это разделение позволяет указывать несколько расширений (например .kmrpt и .kmrpt-file) на один ProgID или менять ProgID при обновлении приложения.

Трёхслойная структура ассоциации файловКлюч расширения — указатель, чьё значение по умолчанию именует ProgID; ProgID — сущность с отображаемым именем, значком и списком verb; значение по умолчанию command под verb — реально запускаемая командная строкаименует ProgID как значение по умолчаниюКлюч расширения .kmrptProgID KomuraSoft.Report.1verb(open и другие под shell)значение по умолчанию commandЗапускается Report.exeТакже хранит отображаемое имя и DefaultIcon

Рис. 1: Ключ расширения — указатель, ProgID — сущность, а command verb — реально запускаемая командная строка.

2.2. HKCR — «объединённое представление» — куда пишете, меняет смысл

Пример выше показан под HKEY_CLASSES_ROOT (HKCR), но HKCR — не физическое место хранения, а объединённое представление HKLM\Software\Classes и HKCU\Software\Classes. Если один и тот же ключ есть в обоих, побеждает сторона HKCU.2

HKCR — объединённое представлениеHKCR — это HKLM и HKCU Classes, наложенные друг на друга; если один и тот же ключ есть в обоих, побеждает HKCU; пишите регистрацию явно в HKLM или HKCU и считайте HKCR только для чтенияHKLM\\Software\\Classes(все пользователи)HKCR(объединённое представление)HKCU\\Software\\Classes(на пользователя)Если ключ один и тот же, побеждает HKCUСчитайте только для чтения(для проверки)

Рис. 2: HKCR — вид HKLM и HKCU Classes, наложенных вместе; всегда называйте одну из сторон как место записи.

Место записи Смысл Нужные права
HKLM\Software\Classes Регистрация общая для всех пользователей Администратор
HKCU\Software\Classes Регистрация только для этого пользователя Нет
Писать прямо в HKCR Распределяется в зависимости от того, где уже живёт существующий ключ Зависит

На практике безопасное деление — всегда писать регистрацию явно в HKLM или HKCU и считать HKCR только для чтения (для проверки). Стоит разобрать и связь с перенаправлением реестра WOW64. Данные ассоциации прямо под HKLM\Software\Classes вроде ключей расширения и ProgID общие между 32- и 64-разрядными представлениями реестра начиная с Windows 7, поэтому 32-разрядный установщик, пишущий их, не уходит на сторону Wow6432Node. Некоторые подразделы COM-регистрации вроде Classes\CLSID, напротив, перенаправляются, и при регистрации расширения оболочки (внутрипроцессный COM) важно деление записи 32/64 бит. Подробности — в «Registry 32-bit/64-bit Redirection and Virtualization Pitfalls — Wow6432Node and the “The Value I Wrote Isn’t There” Problem».

2.3. Регистрация на стороне приложения — App Paths, Applications, RegisteredApplications

Есть и три вида регистрации на стороне приложения, в паре со стороной файла (расширение и ProgID).11

  • App Paths (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths): регистрация, позволяющая ShellExecuteEx запускать только по имени исполняемого файла. Microsoft рекомендует это, потому что не нужно засорять переменную среды PATH.
  • Applications (HKCR\Applications\<app.exe>): задаёт способ открытия по умолчанию, когда произвольный файл передают через «Открыть с помощью», и отображаемое имя приложения (FriendlyAppName).
  • RegisteredApplications + Capabilities: объявляет расширения и типы MIME, которые приложение умеет обрабатывать, и это регистрация, из-за которой оно появляется кандидатом на странице Параметры приложений по умолчанию Windows.

Большинство обращений «наше приложение не появляется в списке приложений по умолчанию» — случаи, когда ProgID зарегистрировали, а эту регистрацию Capabilities пропустили.

Три вида регистрации на стороне приложенияРегистрация на стороне приложения бывает трёх видов — App Paths, Applications и RegisteredApplications — отвечающих соответственно за запуск только по имени файла, способ открытия по умолчанию в «Открыть с помощью» и появление на странице приложений по умолчаниюРегистрация на стороне приложенияApp PathsApplicationsRegisteredApplicationsЗапуск только по имени файлаПо умолчанию в Открыть с помощьюПоявляется на странице приложений по умолчаниюНужно объявление Capabilities

Рис. 3: Регистрация на стороне приложения бывает трёх видов, и чтобы появиться кандидатом приложений по умолчанию, нужна регистрация Capabilities.

2.4. Приложение по умолчанию принадлежит пользователю — защита UserChoice

Запись ProgID как значения по умолчанию ключа расширения сама по себе не делает его приложением по умолчанию. Результат явного выбора пользователя в «Открыть с помощью» и подобном хранится в HKCU\...\Explorer\FileExts\<extension>\UserChoice, и разрешение ассоциации предпочитает ту сторону.

И важный момент: Windows не поддерживает программную смену приложения по умолчанию. Настройки приложения по умолчанию рассчитаны на пользователя через системный интерфейс Параметры; данные UserChoice обфусцированы, а драйвер-фильтр (UCPD.sys) блокирует запись из приложений. В управляемой среде официальное средство — групповая политика / политика MDM.3

То, что использовали инструменты вроде SetUserFTA, которые «имитируют хеш и переписывают», — обратная сторона этой защиты. В установщик внутреннего приложения нужно класть не кражу значения по умолчанию, а три вещи: (a) правильная регистрация ProgID и verb, (b) добавление себя в OpenWithProgIds и (c) при необходимости направление на страницу Параметры.

Разрешение приложения по умолчанию и защита UserChoiceРезультат явного выбора пользователя хранится в UserChoice и предпочитается при разрешении ассоциации; UCPD.sys блокирует перезапись из приложений, поэтому установщик может зарегистрироваться как кандидат и направить на страницу ПараметрыпредпочитаетсяUCPD.sys блокируетUserChoice(выбор пользователя)Разрешение ассоциацииЗначение по умолчанию ключа расширенияПерезапись из приложенияРабота установщикаЗарегистрировать ProgID и verbДобавить в OpenWithProgIdsНаправить на страницу Параметры

Рис. 4: Разрешение ассоциации предпочитает выбор пользователя (UserChoice), и ОС защищает его от перезаписи приложениями.

3. Verb кроме «Открыть» — print, edit, runas, свои verb

Verb — не только open. Стандартные verb, смысл которых знает ОС, включают edit, print, play и preview наряду с open, и стандартный verb автоматически получает отображаемое имя по языку ОС. Verb по умолчанию при двойном щелчке решается в порядке: значение по умолчанию ключа shell → первый verb в реестре → openopenwith.12

Порядок, в котором решается verb по умолчаниюVerb по умолчанию при двойном щелчке — первый найденный в порядке значение по умолчанию ключа shell, первый verb в реестре, open, openwithесли нетесли нетесли нетзначение по умолчанию ключа shellПервый verb в реестреopenopenwith

Рис. 5: Verb по умолчанию при двойном щелчке — первый найденный в этом порядке.

Когда хотите добавить своё действие, зарегистрируйте свой verb.

KomuraSoft.Report.1
   shell
      open
         command
            (Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
      print
         command
            (Default) = "C:\Program Files\KomuraSoft\Report.exe" /print "%1"
      verify                          ← свой verb
         (Default) = Verify report (&V)   ← отображаемое имя в меню
         command
            (Default) = "C:\Program Files\KomuraSoft\Report.exe" /verify "%1"

Три мелких факта, которые полезно знать.

  • Зарегистрируйте verb с именем runas — и определите запуск с повышением, эквивалентный «Запуск от имени администратора»; его же используют API семейства ShellExecute, когда указывают runas.
  • Положите на ключ verb пустое значение с именем Extended — и он станет расширенным verb, показываемым только при Shift+правый щелчок. Удобно прятать опасную редко используемую операцию.12
  • У части ассоциаций старых приложений ещё есть конфигурация, отправляющая документ в существующий процесс через DDE (ключ ddeexec), но запуск verb через DDE уже устаревшее наследие Deprecated. Писать это заново незачем.12

Ещё одна частая авария — кавычки в командной строке. Если элемент командной строки может содержать пробел, его нужно обернуть в кавычки. Это относится и к пути EXE вроде C:\Program Files\..., и %1 (путь выбранного файла) всегда пишите как "%1". Нельзя гарантировать, что путь файла пользователя не содержит пробела. Незаключённый в кавычки My Program.exe толкуется как «запустить My с аргументом Program.exe».13

Авария с кавычками в командной строкеКоманда без кавычек режется по пробелу и ошибочно толкуется как запуск My с аргументом Program.exe, поэтому путь EXE, который может содержать пробел, и %1, обозначающий путь выбранного файла, всегда нужно оборачивать в кавычкирежется по пробелуКоманда без кавычекОшибочно толкуется как запуск другого EXEКоманда в кавычкахЗапускается как задуманоОберните путь EXE в кавычкиВсегда оборачивайте и %1 в кавычки

Рис. 6: Команда без кавычек ошибочно режется по пробелу, поэтому путь EXE и %1 всегда оборачивайте в кавычки.

Механизм только реестра до сих пор (статический verb) можно реализовать, не написав ни одной DLL, и он не рискует сделать Проводник нестабильным. Сама Microsoft неоднократно говорит «прежде чем писать расширение оболочки, подумайте, хватит ли самого простого статического verb, удовлетворяющего требованиям».9

4. Классические расширения оболочки — DLL, которая работает внутри Проводника

4.1. Виды расширений оболочки

Требования, которые статический verb не закрывает — «менять меню динамически в зависимости от выбора», «заменить значок или лист свойств» — используют обработчик расширения оболочки. Типичные виды таковы.4

Обработчик Основной интерфейс Что умеет
Обработчик контекстного меню IContextMenu + IShellExtInit Динамически добавлять и управлять пунктами меню
Обработчик значков / наложение значка IExtractIcon / IShellIconOverlayIdentifier Значок на файл и наложение
Обработчик листа свойств IShellPropSheetExt Добавить вкладку на лист свойств
Эскиз / подсказка IThumbnailProvider / IQueryInfo Вид эскизов и описание при наведении
Обработчик перетаскивания / copy-hook IDropTarget / ICopyHook Вмешиваться при сбросе или копировании/перемещении

Всё это реализовано как классы COM и зарегистрировано в реестре по CLSID. Сама идея COM разобрана в «Что такое COM / ActiveX / OCX - объясняем различия и связь между ними».

4.2. Что значит быть внутрипроцессным COM-сервером

Суть классического расширения оболочки в том, что это внутрипроцессный COM-сервер (DLL), загружаемый в Проводник (или в любое приложение, открывшее общее диалоговое окно файлов). Все оговорки следуют отсюда.4

  • Если расширение падает, Проводник падает вместе с ним. Если оно зависает, правый щелчок замирает на несколько секунд. Ущерб не ограничивается Проводником; он достигает каждого приложения, показавшего диалог открытия файла.
  • Построение меню идёт на потоке UI, поэтому нельзя делать медленную работу вроде доступа к сети или файлового ввода-вывода в момент показа меню.
  • Модель потоков регистрируйте как правило Apartment.
Структура сопутствующего ущерба внутрипроцессного расширенияDLL расширения оболочки загружается не только в Проводник, но и в процесс любого приложения, открывшего диалог файлов, поэтому сбой или зависание расширения распространяются на весь процесс-хостзагружается внутри процессазагружается внутри процессаDLL расширения оболочкиПроводникЛюбое приложение, открывающее диалогСбой или зависание распространяютсяНе делать медленную работу при показе

Рис. 7: DLL расширения работает внутри процесса-хоста, поэтому сбой или зависание распространяются на хост целиком.

Разбирая обращение вроде «Проводник зависает, когда открываю определённую папку» или «правый щелчок занимает пять секунд», нередко выясняется, что причина — стороннее расширение оболочки, а не внутреннее приложение. Методы изоляции — в главе 8.

4.3. Согласовать разрядность — 64-разрядной среде нужна 64-разрядная DLL

Внутрипроцессная DLL должна совпадать по разрядности с процессом, который её загружает. Проводник в 64-разрядной Windows — 64-разрядный процесс, поэтому DLL расширения оболочки, собранная только как 32-разрядная, никогда не загружается и вообще не появляется в меню. Ошибки тоже нет, поэтому это типичная причина «зарегистрировал, а не появляется». Сочетание 32-разрядного тела приложения с 64-разрядной DLL расширения оболочки законно, но нужно следить за тем, что COM-регистрация делится по разрядности (Wow6432Node). Запуск из command verb — отдельный процесс EXE, поэтому этому ограничению не подлежит (оставить 32-разрядный EXE нормально).

Согласование разрядности DLL расширения оболочкиЕдинственная DLL расширения оболочки, которую 64-разрядный Проводник может загрузить, — 64-разрядная; только 32-разрядная DLL не появляется в меню и не даёт ошибки; EXE, запускаемый из command verb, — отдельный процесс и ограничению не подлежитможет загрузитьне может загрузитьотдельный процесс64-разрядный Проводник64-разрядная DLL расширения оболочкиТолько 32-разрядная DLLНе появляется в меню, без ошибкиEXE, запускаемый из verbМожно оставить 32-разрядным

Рис. 8: В 64-разрядный Проводник загружается только 64-разрядная DLL; EXE, запускаемый из verb, этому ограничению не подлежит.

4.4. Почему нельзя писать на управляемом коде

Меня часто спрашивают «можно ли писать расширение оболочки на C#», но Microsoft прямо заявила, что писать внутрипроцессное расширение оболочки на управляемом коде (.NET) не рекомендуется и находится вне поддержки.5

Причина — природа расширения, загружаемого в произвольный процесс. Столкновения версий CLR (особенно ниже .NET Framework 4), повторный вход CLR в цикл сообщений в ожидании блокировки и недетерминированное время жизни объектов из-за сборки мусора, конфликтующее с контрактом счётчика ссылок COM, — структурные причины нестабильности хоста. Часть пунктов смягчена в .NET Framework 4 и новее и в современном .NET, но официальная позиция не изменилась.

Практическое правило простое. Внутрипроцессное расширение пишите на нативном C++. Если нужен управляемый код, сделайте обычный EXE, запускаемый из command verb, или внепроцессное расширение, работающее в отдельном процессе (обработчик предпросмотра и подобное).5

Решение, допустим ли управляемый кодВнутрипроцессное расширение, работающее внутри Проводника, по правилу пишут на нативном C++; если нужен управляемый код, сделайте обычный EXE, запускаемый из command verb, или внепроцессное расширение в отдельном процесседанетРаботает внутри процесса?Пишите на нативном C++Управляемый код допустимРиск CLR / повторного входа делает хост нестабильнымEXE, запускаемый из verbВнепроцессный предпросмотр

Рис. 9: Внутрипроцессное расширение по правилу — нативный C++; управляемый код ограничен конфигурацией, работающей в отдельном процессе.

5. Новое контекстное меню Windows 11 — меню, разделённое надвое

5.1. Что произошло

Windows 11 обновила контекстное меню Проводника. Вырезать, копировать и подобное стали рядом значков сверху; «Открыть» и «Открыть с помощью» сгруппировали сверху; команды, которые добавляет приложение, группируются под стандартными командами оболочки. Когда одно приложение добавляет несколько команд, их собирают во всплывающее меню (подменю) с именем приложения.6

И решающий момент таков. Классические расширения оболочки на IContextMenu не удалили; их перенесли на сторону старого меню, которое открывается «Показать дополнительные параметры» (Shift+F10) и загружает меню Windows 10 как есть.6 Личность начального обращения «меню спрятали» — это это разделение.

Контекстное меню, которое Windows 11 разделила надвоеСначала при правом щелчке открывается новое меню; там появляются только команды, зарегистрированные через IExplorerCommand и идентичность пакета; классические расширения IContextMenu перенесены в старое меню, открываемое «Показать дополнительные параметры»Показать дополнительные параметры Shift+F10Правый щелчок по файлуНовое меню(Windows 11)Команды IExplorerCommand + идентичностьСтарое меню(меню Windows 10)Классические расширения IContextMenuНесколько команд собирают во всплывающее меню

Рис. 10: В новом меню появляются только команды IExplorerCommand + идентичность; классические расширения перенесены на сторону старого меню.

5.2. Официальный путь в новое меню — IExplorerCommand + регистрация в манифесте

Есть один способ поместить свою команду в новое меню. Подготовьте нативную DLL, реализующую интерфейс IExplorerCommand, и объявите COM-сервер и расширение контекстного меню в манифесте пакета MSIX.7

<!-- Манифест пакета (фрагмент) -->
<com:Extension Category="windows.comServer">
  <com:ComServer>
    <com:SurrogateServer DisplayName="Komura commands">
      <com:Class Id="01234567-89AB-CDEF-0123-456789ABCDEF"
                 Path="KomuraCommand.dll" ThreadingModel="STA" />
    </com:SurrogateServer>
  </com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
  <desktop4:FileExplorerContextMenus>
    <desktop5:ItemType Type=".kmrpt">
      <desktop5:Verb Id="VerifyReport"
                     Clsid="01234567-89AB-CDEF-0123-456789ABCDEF" />
    </desktop5:ItemType>
  </desktop4:FileExplorerContextMenus>
</desktop4:Extension>

Type у ItemType может задавать конкретное расширение, или * (все файлы), Directory (папки) или Directory\Background (фон папки). Согласуйте DLL с архитектурой Проводника (64 бит / ARM64).7

Сам IExplorerCommand — интерфейс со времён Windows 7; вы реализуете заголовок (GetTitle), значок (GetIcon), состояние включено / отключено / скрыто (GetState) и выполнение (Invoke). Методы вызываются с потока UI, поэтому доступ к сетевым ресурсам запрещён, а методы построения меню должны возвращаться быстро. Тяжёлую работу делайте после Invoke.147

Структура манифеста регистрации в новом менюОбъявление COM-сервера в манифесте MSIX сопоставляет CLSID с DLL, а объявление расширения контекстного меню связывает цель и реализацию через ItemType и Verb, поэтому своя команда появляется в новом менюсопоставляет CLSID с DLLзадаёт через ItemType и VerbМанифест MSIXОбъявление COM-сервераОбъявление расширения менюDLL реализации IExplorerCommandКоманда появляется в новом менюЦель — расширение, все файлы и подобное

Рис. 11: Два объявления в манифесте связывают DLL реализации с целью, и команда появляется в новом меню.

5.3. Вариант для неупакованного приложения — получить только идентичность через sparse package

Лазейка, когда «наше приложение можно распространять только как MSI; MSIX невозможен», — sparse package (MSIX с внешним расположением). Вы подписываете небольшой MSIX, который есть только манифест, без тела приложения, и регистрируете его в конце существующего установщика. Тогда приложение получает идентичность пакета, и регистрация в манифесте выше (= появление в новом меню) становится возможной. Доступно начиная с Windows 10 версии 2004, пакету нужна подпись сертификатом, которому доверяют на целевой машине.8

Поток получения идентичности через sparse packageПосле того как существующий установщик разместил тело приложения, регистрация sparse package только с манифестом и внешним расположением даёт приложению идентичность пакета и делает возможной регистрацию манифеста нового менюСуществующий установщикРазместить тело приложенияSparse packageТолько манифест, без телаЗарегистрировать с внешним расположениемПолучить идентичность пакетаРегистрация в новом меню становится возможнойНужна доверенная подпись

Рис. 12: Зарегистрируйте sparse package без тела с внешним расположением — и приложение получит идентичность пакета.

Главное преимущество — не нужно менять установщик; это реалистичный ответ для приложения, у которого уже есть актив установщика MSI/EXE. Сравнение с полным переходом на MSIX см. также в «Как выбрать способ распространения Windows-приложения — MSI/MSIX/ClickOnce/xcopy/собственный updater».

5.4. Как verb ассоциации появляются в новом меню

Легко понять неправильно: ассоциации глав 2 и 3 (ProgID и verb) в новом меню ещё живы. Verb по умолчанию при двойном щелчке, «Открыть» и кандидаты «Открыть с помощью» разрешаются из ассоциации и показываются вверху нового меню. Поэтому если нужно только «открывать этим приложением», Windows 11 не требует лишней работы. С другой стороны, ассоциация — не универсальное расширение меню, поэтому произвольную свою команду в первый слой нового меню ставят через IExplorerCommand плюс идентичность — таково разделение ролей.7

Разделение ролей между ассоциациями и новым менюАссоциация ProgID-и-verb в новом меню по-прежнему используется, чтобы разрешить verb по умолчанию, Открыть и Открыть с помощью, и показывается вверху; произвольную свою команду в первый слой нового меню ставят через IExplorerCommand и идентичностьАссоц.(ProgID + verb)Разрешить по умолчанию / ОткрытьВерх нового менюНет лишней работы на Win11Своя командаIExplorerCommand+идентичностьПервый слой нового меню

Рис. 13: Ассоциации по-прежнему отвечают за разрешение семейства «Открыть» в новом меню; только своя команда требует IExplorerCommand плюс идентичность.

6. Практическая таблица решения — какой из трёх вариантов взять

Сводим сказанное в практический выбор из трёх.

Что хотите достичь Рекомендуемое средство Как выглядит в Windows 11 Нужная работа и стоимость
(a) Запускать внутреннее приложение двойным щелчком или «Открыть» Ассоциация + статический verb (только регистрация в реестре) Встроено в «Открыть» и «Открыть с помощью» в новом меню Только регистрация в реестре установщиком. Нет DLL, нет лишнего требования подписи
(b) Поместить свою команду для выбранного файла/папки в новое меню Реализация IExplorerCommand + регистрация в манифесте MSIX. Неупакованному приложению дают идентичность через sparse package Первый слой нового меню (несколько команд собирают во всплывающее меню с именем приложения) Нативная C++ DLL + идентичность пакета + подпись кода
(c) Продолжать использовать существующее классическое расширение IContextMenu Пока оставить как есть (не выбирать для новой разработки) Только сторона старого меню, под «Показать дополнительные параметры» (Shift+F10) Поддерживать 64-разрядную сборку и COM-регистрацию. Планировать поздний переход на (b)

Два пункта суждения. Первое: не тащите (b) или (c) под требование, которое закрывает (a). В момент, когда пишете расширение оболочки, вы берёте ответственность за стабильность Проводника. Второе: (c) лишь «не сломано»; как пользовательский опыт оно остаётся на шаг хуже. Чем чаще команда используется в повседневной работе, тем больше отдача от перехода на (b).

Как выбирать среди трёх вариантовЕсли нужен только запуск двойным щелчком или Открыть, хватит ассоциации и статического verb; чтобы поместить свою команду в новое меню, используйте IExplorerCommand и регистрацию в манифесте MSIX; если нельзя стать MSIX, выдайте идентичность через sparse package; существующее классическое расширение IContextMenu пока оставьте на стороне старого менюданетдаданетнетХватит Открыть?Ассоц. + статический verbСвоя команда в новом меню?Можете стать MSIX?IExplorerCommand+MSIXИдентичность sparse-pkgПока оставить классическоеТолько сторона старого менюНет DLL, малый риск

Рис. 14: Выбирайте среди статического verb, IExplorerCommand плюс идентичность и сохранения классического — по требованию.

7. Развёртывание и регистрация на практике — установщик, sparse package, очистка

7.1. HKLM или HKCU

Согласуйте с формой установщика. Все пользователи (размещено под Program Files, права администратора) — HKLM\Software\Classes; установка на пользователя (без повышения) — HKCU\Software\Classes. Смешаете — получите обращение «у A открывается, у B нет». Для расширения оболочки с регистрацией CLSID Reg-Free COM — убирающий саму регистрацию в реестре — годится для COM внутри приложения, но неприменим к расширению оболочки, которое загружает Проводник, поэтому нужна прямая регистрация («Что такое Reg-Free COM — механизм использования COM без регистрации»).

7.2. После изменения уведомите — SHChangeNotify

После регистрации, изменения или удаления ассоциации уведомите событие SHCNE_ASSOCCHANGED через SHChangeNotify. Пропустите это — и Проводник может не заметить изменение до перезагрузки.110

// Вызвать один раз после смены ассоциаций, напр. из пользовательского действия установщика
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);

7.3. Регистрация и снятие sparse package

Регистрация и снятие sparse package — работа установщика. Регистрируйте после размещения файлов; снимайте до удаления файлов.8

# При установке: после размещения файлов зарегистрировать папку установки как внешнее расположение
Add-AppxPackage -Path "C:\Program Files\KomuraSoft\KomuraReport.identity.msix" `
                -ExternalLocation "C:\Program Files\KomuraSoft"

# При удалении: снять регистрацию пакета до удаления файлов
Remove-AppxPackage <package full name>

На что смотреть: Add-AppxPackage регистрирует для пользователя, который его выполнил. Если вызвать из пользовательского действия MSI на машину под LocalSystem, идентичность не выдаётся установившему пользователю, поэтому настраивают выполнение под олицетворением пользователя. Даже тогда регистрация под олицетворением — только для пользователя, выполнившего эту установку. На ПК с несколькими пользователями у других и у созданных позже пользователей нет идентичности пакета, и команда не появляется в новом меню. Чтобы каждый пользователь мог пользоваться, дайте механизм вроде проверки своей регистрации пакета при первом запуске и регистрации, если её нет (регистрация на пользователя), и включите снятие у каждого пользователя с регистрацией в план удаления. Чтобы отразить регистрацию манифеста, может понадобиться перезапуск Проводника (или выход).7

Порядок регистрации и снятия sparse packageПри установке регистрируйте sparse package после размещения файлов; при удалении снимайте регистрацию до удаления файлов; следите за тем, что регистрация действует только для пользователя, который её выполнилУстановкаРазместить файлыЗарегистрировать sparse packageУдалениеСнять регистрацию пакетаУдалить файлыРегистрация действует только для текущего пользователя

Рис. 15: Регистрируйте после размещения файлов, снимайте до удаления и следите за тем, что регистрация — на текущего пользователя.

7.4. Очистка при удалении — что удалять и что оставлять

У очистки при удалении есть ясная линия официальной рекомендации.1

  • Удалять: весь внутренний ключ ProgID, регистрацию Capabilities/RegisteredApplications, регистрацию CLSID расширения оболочки, sparse package (Remove-AppxPackage).
  • Оставлять: значение по умолчанию ключа расширения (.kmrpt). Официальная рекомендация — не удалять его, даже если оно всё ещё указывает на внутренний ProgID. После установки трудно судить, не забрало ли другое приложение значение по умолчанию, а Windows просто игнорирует незарегистрированный ProgID значения по умолчанию, так что оставлять его не вредно.
  • Вызовите SHChangeNotify(SHCNE_ASSOCCHANGED) и в конце очистки.

Большинство проблем «удалили, а в меню ещё остатки» — утечка в этом проекте очистки.

Проект очистки при удаленииПри удалении удаляете внутренний ключ ProgID, регистрацию CLSID и sparse package; оставляете значение по умолчанию ключа расширения, потому что незарегистрированный ProgID игнорируется; в конце очистки уведомляете об изменении через SHChangeNotifyУдалениеУдалитьОставитьРегистрация ProgID и CLSIDSparse packageЗначение по умолчанию ключа расширенияНезарегистрированный ProgID игнорируетсяВ конце уведомить через SHChangeNotify

Рис. 16: Удалите внутреннюю регистрацию, оставьте значение по умолчанию ключа расширения и в конце очистки уведомите об изменении.

8. Устранение неполадок — нет, дубль, тяжело

8.1. Не появляется в меню

Изолируйте в таком порядке.

  1. Какое меню смотрите: регистрация в классическом стиле появляется только на стороне старого меню под Shift+F10. Сначала проверьте оба.
  2. Разрядность: только 32-разрядная DLL расширения оболочки не загружается в 64-разрядный Проводник (раздел 4.3).
  3. Место регистрации: HKLM/HKCU, путаница Wow6432Node. Подтвердите фактический ключ через reg query.
  4. Регистрация пакета: для нового меню подтвердите наличие через Get-AppxPackage, доверие сертификата подписи и путь -ExternalLocation, затем перезапустите Проводник.7
  5. Пропущенное уведомление: если забыли SHChangeNotify, это видно по тому, помогает ли перезапуск Проводника.
Порядок изоляции, когда не появляется в менюНачните с подтверждения, какое меню смотрите, затем изолируйте разрядность DLL, место регистрации в реестре, регистрацию пакета и подпись, и пропущенный SHChangeNotify, в этом порядкеПодтвердить, какое меню, старое или новоеПодтвердить разрядность DLLПодтвердить место регистрации HKLM и HKCUПодтвердить регистрацию пакета и подписьРаспознать пропущенное уведомление перезапуском

Рис. 17: Когда «не появляется», изолируйте в порядке меню, которое смотрите, разрядность, место регистрации, регистрация пакета, пропущенное уведомление.

8.2. Появляется дважды или не исчезает

Типичные причины — сосуществование классической регистрации в реестре и регистрации в манифесте, утечка очистки при удалении (раздел 7.4) или остатки ProgID старой версии. Если дважды только в старом меню — думайте об остатках; если на старом и новом — о сосуществовании.

Изоляция двойного отображенияДважды только в старом меню указывает на остатки вроде утечки очистки или старого ProgID; дважды на старом и новом указывает на сосуществование классической регистрации в реестре и регистрации в манифестетолько старое менюстарое и новоеГде появляется дважды?ОстаткиСосуществованиеУтечка очистки или оставленный старый ProgIDКлассическая регистрация в реестре сосуществует с новой

Рис. 18: Дважды только в старом меню указывает на остатки; дважды на старом и новом — на сосуществование.

8.3. Проводник тяжёлый или падает

Когда правый щелчок медленный или определённая папка падает, сначала составьте опись установленных расширений оболочки. Перечислите расширения не от Microsoft инструментом вроде ShellExView от NirSoft, временно отключите подозрительные и найдите виновную DLL двоичным поиском. При сбое подсказкой служит и «Faulting module» в Просмотре событий. Если причиной было внутреннее расширение, подозревайте синхронный ввод-вывод или доступ к сети на пути построения меню (разделы 4.2 и 5.2).

Поиск виновной DLL, когда тяжело или падаетПеречислите расширения оболочки не от Microsoft в ShellExView, временно отключите подозрительные и найдите виновную DLL двоичным поиском; при сбое подсказкой служит и faulting module в Просмотре событийОпись расширений оболочкиПеречислить не от MicrosoftВременно отключить и двоичный поискНайти виновную DLLПри сбоеПроверить faulting module

Рис. 19: Временно отключите расширения не от Microsoft и ищите двоично; при сбое используйте и Просмотр событий.

8.4. Для проверки удобна Windows Sandbox

Проверка интеграции с оболочкой строится на подтверждении «установить в чистой среде → поработать → удалить → остатков ноль». Здесь удобна Windows Sandbox (Pro/Enterprise/Education): каждый запуск за несколько секунд поднимает совершенно новую одноразовую Windows, поэтому тесты регистрации и очистки установщика можно гонять сколько угодно. Закроете — всё исчезнет, поэтому подходит и для расследования остаточного реестра.15

9. Итог

  • Ассоциация файлов — трёхслойная структура «ключ расширения → ProgID → verb», а HKCR — объединённое представление HKLM/HKCU Classes. Называйте место записи явно и всегда оборачивайте %1 в кавычки.
  • Приложение по умолчанию рассчитано на выбор пользователя и не меняется из программы. Работа установщика — правильно зарегистрироваться как кандидат.
  • Классическое расширение оболочки — внутрипроцессная COM DLL, загружаемая в Проводник. Сбой или задержка распространяются на целое; нужны 64 бита; управляемый код не поддерживается; правило — реализация на нативном C++.
  • В Windows 11 контекстное меню разделилось надвое. Чтобы поместить свою команду в новое меню, нужны IExplorerCommand плюс манифест MSIX; классический IContextMenu перенесён на сторону «Показать дополнительные параметры».
  • Для приложения, которое не может стать MSIX, получение идентичности через sparse package (MSIX с внешним расположением) — реалистичный ответ.
  • Если нужно только «открыть этим приложением», ассоциации и статического verb по-прежнему достаточно. Начинать с самого простого средства — и официальная рекомендация.
  • После регистрации, изменения или удаления уведомите через SHChangeNotify; при удалении удалите ProgID, но оставьте значение по умолчанию ключа расширения. Для проверки удобна Windows Sandbox.

Если замена ПК на Windows 11 заставила заметить, что «меню спрятали», сначала подтвердите, какой это из (a), (b) и (c) в таблице решения главы 6. Масштаб работы должно быть видно на месте.

Похожие статьи

Смежные области консультирования

Компания KomuraSoft LLC занимается проектированием и реализацией ассоциации файлов, контекстных меню и расширений оболочки для деловых приложений; нацеливанием на новое контекстное меню Windows 11 (переход на IExplorerCommand, внедрение sparse package); проверкой регистрации и очистки существующего установщика; и расследованием причины, когда Проводник тяжёлый или падает. Можно начать с решения, что делать с «меню, спрятанным под Показать дополнительные параметры».

Справочные ссылки

  1. Microsoft Learn, File Types. О структуре ключа расширения, указывающего на ProgID; OpenWithProgIds; делении регистрации между HKLM/HKCU\Software\Classes; вызове SHChangeNotify(SHCNE_ASSOCCHANGED) после смены ассоциации; и удалении ProgID при удалении с оставлением значения по умолчанию ключа расширения.  2 3 4 5

  2. Microsoft Learn, HKEY_CLASSES_ROOT Key. О том, что HKEY_CLASSES_ROOT — объединённое представление HKLM\Software\Classes и HKCU\Software\Classes; определения на стороне пользователя имеют приоритет над машинными; и правилах распределения при записи.  2

  3. Microsoft Learn, Windows app defaults platform. О том, что смена приложения по умолчанию рассчитана только на системный интерфейс Параметры; данные пользовательских настроек обфусцированы и защищены от записи драйвером-фильтром (UCPD.sys); смена на основе реестра не поддерживается; и в управляемой среде используют групповую политику / политику MDM.  2

  4. Microsoft Learn, Working with Shell Extensions. О видах обработчиков расширений оболочки; расширении как внутрипроцессной COM DLL, загружаемой в Проводник (и в процессы, хостящие оболочку), так что сбой или зависание распространяются на Проводник целиком; регистрации с ThreadingModel=Apartment; и рассмотрении более простой альтернативы до расширения оболочки.  2 3

  5. Microsoft Learn, Guidance for Implementing In-Process Extensions. О том, что Microsoft не рекомендует и не поддерживает внутрипроцессную реализацию расширения оболочки на управляемом коде; причинах, включая столкновения версий CLR, повторный вход и недетерминированное время жизни объектов; и допустимости управляемого кода для внепроцессного расширения (обработчик предпросмотра или запуск из shell\verb\command).  2 3

  6. Windows Developer Blog, Extending the Context Menu and Share Dialog in Windows 11. О проекте нового контекстного меню Windows 11; расширении через IExplorerCommand плюс идентичность приложения; размещении «Открыть» и «Открыть с помощью» сверху; сборе нескольких команд во всплывающее меню с именем приложения; и классических расширениях IContextMenu, загружаемых как меню Windows 10 под «Показать дополнительные параметры» (Shift+F10).  2 3

  7. Microsoft Learn, Add a File Explorer context menu command to a packaged desktop app. О том, что регистрация в новом контекстном меню Windows 11 делается реализацией IExplorerCommand плюс windows.comServer плюс объявлением манифеста desktop4:FileExplorerContextMenus; ItemType может задавать *, Directory или Directory\Background; согласовании архитектуры DLL; сохранении методов построения меню быстрыми; покрытии неупакованного приложения через sparse package; иногда нужном перезапуске Проводника, чтобы регистрация вступила в силу; и том, что ассоциация файлов — не универсальное расширение меню.  2 3 4 5 6 7 8

  8. Microsoft Learn, Grant package identity by packaging with external location. О получении идентичности пакета регистрацией пакета с внешним расположением (sparse package) без смены существующего установщика; доступности начиная с Windows 10 версии 2004; и функциях Windows, требующих идентичности (регистрация в контекстном меню, уведомления и подобное), которые становятся доступны.  2 3

  9. Microsoft Learn, Choosing a Static or Dynamic Shortcut Menu Method. О выборе самого простого метода статического verb, удовлетворяющего требованиям; IContextMenu как самом мощном, но и самом сложном и отнесённом к нерекомендуемой стороне; и IExplorerCommand/IExplorerCommandState как рекомендуемом методе.  2

  10. Microsoft Learn, SHChangeNotify function. О том, как поднять событие SHCNE_ASSOCCHANGED, уведомляющее систему о смене ассоциации файлов, и как использовать его, чтобы оболочка заметила изменение.  2

  11. Microsoft Learn, Application Registration. О том, что регистрация исполняемого файла через подраздел App Paths рекомендуется; роли подраздела Applications; регистрации verb через SystemFileAssociations; и приоритете ProgID и связанных сведений при смене приложения по умолчанию. 

  12. Microsoft Learn, Creating Shortcut Menu Handlers. О способе регистрации статического verb; порядке решения verb по умолчанию (значение по умолчанию → первый verb → Open → Open With); отображаемых именах стандартных verb, которые даёт ОС; расширенных verb через Extended; ассоциации с командами DDE, которая Deprecated; и оговорках перенаправления WOW64 в 64-разрядной среде.  2 3

  13. Microsoft Learn, Verbs and File Associations. О том, что verb — действие, используемое и ShellExecuteEx; элементы командной строки, которые могут содержать пробел, нужно оборачивать в кавычки, а “%1” всегда писать в кавычках; и регистрации процедуры по умолчанию под HKCR\Applications. 

  14. Microsoft Learn, IExplorerCommand interface. О составе методов GetTitle, GetIcon, GetState, Invoke, EnumSubCommands и подобных; методах, вызываемых на потоке UI, поэтому они не должны общаться с сетевыми ресурсами; и доступности начиная с Windows Vista. 

  15. Microsoft Learn, Windows Sandbox. О возможности за несколько секунд запустить одноразовую изолированную среду Windows, обо всех изменениях, отбрасываемых при закрытии, пригодности для тестирования ПО и проверки установщика, и доступности на Pro/Enterprise/Education. 

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Почему пункт контекстного меню нашего приложения в Windows 11 появляется только в «Показать дополнительные параметры»?
Потому что в Windows 11 контекстное меню Проводника разделилось на два слоя, старый и новый. В новом меню могут появиться только команды, которые реализуют интерфейс IExplorerCommand и зарегистрированы в манифесте пакета MSIX (= имеют идентичность пакета). Классические расширения оболочки на IContextMenu перенесены в старое меню, которое открывается «Показать дополнительные параметры» (Shift+F10). Само расширение не сломано, поэтому пока продолжает работать, но чтобы оно было в новом меню, нужна миграция на IExplorerCommand и либо упаковка в MSIX, либо выдача идентичности через sparse package.
Может ли установщик назначить наше приложение приложением по умолчанию для файла (тем, что открывается двойным щелчком)?
Нет. Выбор приложения по умолчанию рассчитан на пользователя, и Windows не поддерживает смену приложения по умолчанию ниоткуда, кроме системного интерфейса Параметры. Сведения UserChoice, которые хранят выбор каждого пользователя, обфусцированы, а драйвер-фильтр (UCPD.sys) ещё и защищает их от записи приложениями. Установщик может зарегистрировать ProgID и verb, добавить себя в OpenWithProgIds, чтобы появиться кандидатом в «Открыть с помощью», и направить пользователя на страницу приложений по умолчанию. Правильная реализация — не украсть значение по умолчанию, а подготовиться к тому, чтобы вас выбрали.
Можно ли писать расширение оболочки на управляемом коде, например на C#?
Microsoft прямо заявила, что писать внутрипроцессное расширение оболочки (обработчик контекстного меню, обработчик значков и подобное) на управляемом коде не рекомендуется и не поддерживается. Расширение загружается в Проводник и в процесс любого приложения, открывшего общее диалоговое окно файлов, поэтому столкновения версий CLR, повторный вход и недетерминированное время жизни объектов делают хост нестабильным. Правило — реализация на нативном C++. Обычный EXE, запускаемый из command verb, или внепроцессное расширение вроде обработчика предпросмотра, работающее в отдельном процессе, на управляемом коде допустимы.
Что такое sparse package (MSIX с внешним расположением)?
Небольшой пакет MSIX, который не содержит файлов приложения, только манифест (сведения об идентичности). Для приложения, установленного обычным установщиком (MSI, Inno Setup и подобными), его регистрируют через Add-AppxPackage -ExternalLocation, указывая папку установки, и приложение получает идентичность пакета и может использовать функции, которые её требуют: регистрацию в новом контекстном меню Windows 11 и всплывающие уведомления. Доступно начиная с Windows 10 версии 2004, пакету нужна доверенная на целевой машине подпись кода. Это реалистичный вариант, когда нужна поддержка нового меню без перевода всей поставки на MSIX.
Что делать, если пункт контекстного меню появляется дважды или не исчезает?
Сначала изолируйте причину, проверив, на каком меню он появляется: на новом или на старом (Показать дополнительные параметры). Типичное двойное отображение — сосуществование классической регистрации в реестре и регистрации в манифесте MSIX либо оставшаяся при удалении регистрация ProgID или CLSID расширения. После смены ассоциации подозревайте также пропущенный SHChangeNotify(SHCNE_ASSOCCHANGED); сразу после регистрации пакета — пропущенный перезапуск Проводника. Если это не помогает, временно отключите расширения не от Microsoft в ShellExView и найдите виновную DLL двоичным поиском.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог