From de29186d0edf94ab31bd7926b83eea28aaa0a925 Mon Sep 17 00:00:00 2001 From: Fantom TM Date: Sun, 15 Feb 2026 16:04:52 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D1=8C=D1=82?= =?UTF-8?q?=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ContextMenuRegistrar.cs | 430 ++++++++++++++++++++++++++++++++++++ ContextMenuRegistrar.csproj | 13 ++ ContextMenuRegistrar.slnx | 3 + 3 files changed, 446 insertions(+) create mode 100644 ContextMenuRegistrar.cs create mode 100644 ContextMenuRegistrar.csproj create mode 100644 ContextMenuRegistrar.slnx diff --git a/ContextMenuRegistrar.cs b/ContextMenuRegistrar.cs new file mode 100644 index 0000000..65d393a --- /dev/null +++ b/ContextMenuRegistrar.cs @@ -0,0 +1,430 @@ +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 +} \ No newline at end of file diff --git a/ContextMenuRegistrar.csproj b/ContextMenuRegistrar.csproj new file mode 100644 index 0000000..3c22345 --- /dev/null +++ b/ContextMenuRegistrar.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + latest + + + + + + + + diff --git a/ContextMenuRegistrar.slnx b/ContextMenuRegistrar.slnx new file mode 100644 index 0000000..8ef79dc --- /dev/null +++ b/ContextMenuRegistrar.slnx @@ -0,0 +1,3 @@ + + +