using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Security.Principal; using Microsoft.Win32; /// /// Предоставляет методы для регистрации и удаления пунктов контекстного меню Windows. /// public static class ContextMenuRegistrar { #region Проверка прав администратора private static void CheckAdministrator() { using WindowsIdentity identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); if (!principal.IsInRole(WindowsBuiltInRole.Administrator)) throw new UnauthorizedAccessException("Требуются права администратора."); } #endregion #region Вспомогательный метод для регистрации с прямой командой private static void RegisterWithCommand(string rootPath, string menuName, string command, List createdKeys) { string shellPath = rootPath; // уже включает Shell, например @"SystemFileAssociations\.mp4\Shell" using (RegistryKey shellKey = Registry.ClassesRoot.CreateSubKey(shellPath)) using (RegistryKey menuKey = shellKey.CreateSubKey(menuName)) { menuKey.SetValue(null, menuName); using RegistryKey commandKey = menuKey.CreateSubKey("command"); commandKey.SetValue(null, command); } createdKeys.Add($@"{shellPath}\{menuName}"); } #endregion #region Регистрация для файлов по расширениям /// /// Регистрирует пункт меню для указанных расширений файлов, используя текущее приложение. /// /// Имя пункта меню (отображается в контекстном меню). /// Коллекция расширений, например {".mp4", ".avi"}. /// Необязательный префикс командной строки, передаваемый перед именем файла. /// Приложение не запущено от имени администратора. /// Некорректное имя меню или расширение. /// extensions равно null. /// Ошибка при регистрации (выполнен откат). public static void RegisterFileExtensions(string menuName, IEnumerable extensions, string commandPrefix = "") { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterFileExtensionsForExe(menuName, extensions, appPath, commandPrefix); } /// /// Регистрирует пункт меню для указанных расширений файлов с заданным исполняемым файлом. /// /// Имя пункта меню. /// Коллекция расширений. /// Полный путь к исполняемому файлу. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню, расширение или путь к exe. /// extensions равно null. /// Ошибка при регистрации (выполнен откат). public static void RegisterFileExtensionsForExe(string menuName, IEnumerable extensions, string exePath, string commandPrefix = "") { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = $"\"{exePath}\"{(string.IsNullOrEmpty(commandPrefix) ? "" : " " + commandPrefix)} \"%1\""; RegisterFileExtensionsCommand(menuName, extensions, command); } /// /// Регистрирует пункт меню для указанных расширений файлов с заданной полной командой. /// /// Имя пункта меню. /// Коллекция расширений. /// Полная командная строка, которая будет записана в ключ "command". /// Приложение не запущено от имени администратора. /// Некорректное имя меню, расширение или пустая команда. /// extensions равно null. /// Ошибка при регистрации (выполнен откат). public static void RegisterFileExtensionsCommand(string menuName, IEnumerable extensions, string command) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); if (extensions == null) throw new ArgumentNullException(nameof(extensions)); if (string.IsNullOrWhiteSpace(command)) throw new ArgumentException("Команда не может быть пустой.", nameof(command)); List createdKeys = []; try { foreach (string ext in extensions) { if (string.IsNullOrWhiteSpace(ext) || !ext.StartsWith(".")) throw new ArgumentException($"Некорректное расширение: '{ext}'. Должно начинаться с точки.", nameof(extensions)); string shellPath = $@"SystemFileAssociations\{ext}\Shell"; RegisterWithCommand(shellPath, menuName, command, createdKeys); } } catch (Exception ex) { // Откат: удаляем созданные ключи foreach (string keyPath in createdKeys) { try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { } } throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex); } } /// /// Удаляет ранее зарегистрированный пункт меню для указанных расширений файлов. /// /// Имя пункта меню. /// Коллекция расширений. /// Приложение не запущено от имени администратора. /// Некорректное имя меню или расширение. /// extensions равно null. /// Не удалось удалить одно или несколько расширений. public static void UnregisterFileExtensions(string menuName, IEnumerable extensions) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); if (extensions == null) throw new ArgumentNullException(nameof(extensions)); List errors = []; foreach (string ext in extensions) { if (string.IsNullOrWhiteSpace(ext) || !ext.StartsWith(".")) throw new ArgumentException($"Некорректное расширение: '{ext}'. Должно начинаться с точки.", nameof(extensions)); string keyPath = $@"SystemFileAssociations\{ext}\Shell\{menuName}"; try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch (ArgumentException) { /* ключ не существует – игнорируем */ } catch (Exception ex) { errors.Add($"{ext}: {ex.Message}"); } } if (errors.Any()) throw new InvalidOperationException("Не удалось удалить некоторые расширения: " + string.Join("; ", errors)); } #endregion #region Регистрация для всех файлов /// /// Регистрирует пункт меню для всех файлов, используя текущее приложение. /// /// Имя пункта меню. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню. /// Ошибка при регистрации. public static void RegisterAllFiles(string menuName, string commandPrefix = "") { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterAllFilesForExe(menuName, appPath, commandPrefix); } /// /// Регистрирует пункт меню для всех файлов с заданным исполняемым файлом. /// /// Имя пункта меню. /// Полный путь к исполняемому файлу. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню или путь к exe. /// Ошибка при регистрации. public static void RegisterAllFilesForExe(string menuName, string exePath, string commandPrefix = "") { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = $"\"{exePath}\"{(string.IsNullOrEmpty(commandPrefix) ? "" : " " + commandPrefix)} \"%1\""; RegisterAllFilesCommand(menuName, command); } /// /// Регистрирует пункт меню для всех файлов с заданной полной командой. /// /// Имя пункта меню. /// Полная командная строка, которая будет записана в ключ "command". /// Приложение не запущено от имени администратора. /// Некорректное имя меню или пустая команда. /// Ошибка при регистрации. public static void RegisterAllFilesCommand(string menuName, string command) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); if (string.IsNullOrWhiteSpace(command)) throw new ArgumentException("Команда не может быть пустой.", nameof(command)); List createdKeys = []; try { string shellPath = @"*\Shell"; RegisterWithCommand(shellPath, menuName, command, createdKeys); } catch (Exception ex) { foreach (string keyPath in createdKeys) { try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { } } throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex); } } /// /// Удаляет пункт меню для всех файлов. /// /// Имя пункта меню. /// Приложение не запущено от имени администратора. /// Некорректное имя меню. public static void UnregisterAllFiles(string menuName) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); string keyPath = $@"*\Shell\{menuName}"; try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch (ArgumentException) { } } #endregion #region Регистрация для папок (щелчок на иконке папки) /// /// Регистрирует пункт меню для папок (появляется при щелчке на иконке папки), используя текущее приложение. /// /// Имя пункта меню. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню. /// Ошибка при регистрации. public static void RegisterFolderShell(string menuName, string commandPrefix = "") { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterFolderShellForExe(menuName, appPath, commandPrefix); } /// /// Регистрирует пункт меню для папок с заданным исполняемым файлом. /// /// Имя пункта меню. /// Полный путь к исполняемому файлу. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню или путь к exe. /// Ошибка при регистрации. public static void RegisterFolderShellForExe(string menuName, string exePath, string commandPrefix = "") { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = $"\"{exePath}\"{(string.IsNullOrEmpty(commandPrefix) ? "" : " " + commandPrefix)} \"%1\""; RegisterFolderShellCommand(menuName, command); } /// /// Регистрирует пункт меню для папок с заданной полной командой. /// /// Имя пункта меню. /// Полная командная строка, которая будет записана в ключ "command". /// Приложение не запущено от имени администратора. /// Некорректное имя меню или пустая команда. /// Ошибка при регистрации. public static void RegisterFolderShellCommand(string menuName, string command) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); if (string.IsNullOrWhiteSpace(command)) throw new ArgumentException("Команда не может быть пустой.", nameof(command)); List createdKeys = []; try { string shellPath = @"Directory\Shell"; RegisterWithCommand(shellPath, menuName, command, createdKeys); } catch (Exception ex) { foreach (string keyPath in createdKeys) { try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { } } throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex); } } /// /// Удаляет пункт меню для папок (из раздела Shell). /// /// Имя пункта меню. /// Приложение не запущено от имени администратора. /// Некорректное имя меню. public static void UnregisterFolderShell(string menuName) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); string keyPath = $@"Directory\Shell\{menuName}"; try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch (ArgumentException) { } } #endregion #region Регистрация для фона папки (щелчок на свободном месте внутри папки) /// /// Регистрирует пункт меню для фона папки (появляется при щелчке на свободном месте внутри открытой папки), /// используя текущее приложение. В команде рекомендуется использовать параметр "%V" для передачи пути к текущей папке. /// /// Имя пункта меню. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню. /// Ошибка при регистрации. public static void RegisterFolderBackgroundShell(string menuName, string commandPrefix = "") { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterFolderBackgroundShellForExe(menuName, appPath, commandPrefix); } /// /// Регистрирует пункт меню для фона папки с заданным исполняемым файлом. /// В команде рекомендуется использовать параметр "%V" для передачи пути к текущей папке. /// /// Имя пункта меню. /// Полный путь к исполняемому файлу. /// Необязательный префикс командной строки. /// Приложение не запущено от имени администратора. /// Некорректное имя меню или путь к exe. /// Ошибка при регистрации. public static void RegisterFolderBackgroundShellForExe(string menuName, string exePath, string commandPrefix = "") { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; // Для фона папки рекомендуется %V string command = $"\"{exePath}\"{(string.IsNullOrEmpty(commandPrefix) ? "" : " " + commandPrefix)} \"%V\""; RegisterFolderBackgroundShellCommand(menuName, command); } /// /// Регистрирует пункт меню для фона папки с заданной полной командой. /// /// Имя пункта меню. /// Полная командная строка, которая будет записана в ключ "command". Рекомендуется использовать "%V". /// Приложение не запущено от имени администратора. /// Некорректное имя меню или пустая команда. /// Ошибка при регистрации. public static void RegisterFolderBackgroundShellCommand(string menuName, string command) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); if (string.IsNullOrWhiteSpace(command)) throw new ArgumentException("Команда не может быть пустой.", nameof(command)); List createdKeys = []; try { string shellPath = @"Directory\Background\Shell"; RegisterWithCommand(shellPath, menuName, command, createdKeys); } catch (Exception ex) { foreach (string keyPath in createdKeys) { try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { } } throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex); } } /// /// Удаляет пункт меню для фона папки. /// /// Имя пункта меню. /// Приложение не запущено от имени администратора. /// Некорректное имя меню. public static void UnregisterFolderBackgroundShell(string menuName) { CheckAdministrator(); if (string.IsNullOrWhiteSpace(menuName)) throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName)); string keyPath = $@"Directory\Background\Shell\{menuName}"; try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch (ArgumentException) { } } #endregion }