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 string BuildCommand(string exePath, string prefix, string argument) { prefix ??= ""; return $"\"{exePath}\"{(string.IsNullOrEmpty(prefix) ? "" : " " + prefix)} {argument}"; } // Возвращает путь к иконке для exe-методов private static string? GetIconPath(string exePath, bool addIcon) { return addIcon ? $"{exePath},0" : null; } // Регистрация с возможным указанием иконки (iconPath может быть null) private static void RegisterWithCommand(string rootPath, string menuName, string command, List createdKeys, string? iconPath = null) { using (RegistryKey shellKey = Registry.ClassesRoot.CreateSubKey(rootPath)) using (RegistryKey menuKey = shellKey.CreateSubKey(menuName)) { menuKey.SetValue(null, menuName); if (!string.IsNullOrWhiteSpace(iconPath)) menuKey.SetValue("Icon", iconPath!); using RegistryKey commandKey = menuKey.CreateSubKey("command"); commandKey.SetValue(null, command); } createdKeys.Add($@"{rootPath}\{menuName}"); } #endregion #region Регистрация для файлов по расширениям /// /// Регистрирует пункт меню для указанных расширений файлов, используя текущее приложение. /// /// Имя пункта меню. /// Коллекция расширений, например {".mp4", ".avi"}. /// Необязательный префикс командной строки. /// Если true, в качестве иконки используется текущее приложение (индекс 0). public static void RegisterFileExtensions(string menuName, IEnumerable extensions, string commandPrefix = "", bool addIcon = false) { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterFileExtensionsForExe(menuName, extensions, appPath, commandPrefix, addIcon); } /// /// Регистрирует пункт меню для указанных расширений файлов с заданным исполняемым файлом. /// /// Имя пункта меню. /// Коллекция расширений. /// Полный путь к исполняемому файлу. /// Необязательный префикс командной строки. /// Если true, в качестве иконки используется указанный exePath (индекс 0). public static void RegisterFileExtensionsForExe(string menuName, IEnumerable extensions, string exePath, string commandPrefix = "", bool addIcon = false) { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = BuildCommand(exePath, commandPrefix, "\"%1\""); string? iconPath = GetIconPath(exePath, addIcon); RegisterFileExtensionsCommand(menuName, extensions, command, iconPath); } /// /// Регистрирует пункт меню для указанных расширений файлов с заданной полной командой. /// /// Имя пункта меню. /// Коллекция расширений. /// Полная командная строка. /// Необязательный путь к иконке. Если не указан (null), иконка не добавляется. public static void RegisterFileExtensionsCommand(string menuName, IEnumerable extensions, string command, string? iconPath = null) { 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, iconPath); } } catch (Exception ex) { foreach (string keyPath in createdKeys) { try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { } } throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex); } } /// /// Удаляет ранее зарегистрированный пункт меню для указанных расширений файлов. /// 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 = "", bool addIcon = false) { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterAllFilesForExe(menuName, appPath, commandPrefix, addIcon); } /// /// Регистрирует пункт меню для всех файлов с заданным исполняемым файлом. /// public static void RegisterAllFilesForExe(string menuName, string exePath, string commandPrefix = "", bool addIcon = false) { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = BuildCommand(exePath, commandPrefix, "\"%1\""); string? iconPath = GetIconPath(exePath, addIcon); RegisterAllFilesCommand(menuName, command, iconPath); } /// /// Регистрирует пункт меню для всех файлов с заданной полной командой. /// public static void RegisterAllFilesCommand(string menuName, string command, string? iconPath = null) { 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, iconPath); } 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 = "", bool addIcon = false) { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterFolderShellForExe(menuName, appPath, commandPrefix, addIcon); } /// /// Регистрирует пункт меню для папок с заданным исполняемым файлом. /// public static void RegisterFolderShellForExe(string menuName, string exePath, string commandPrefix = "", bool addIcon = false) { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = BuildCommand(exePath, commandPrefix, "\"%1\""); string? iconPath = GetIconPath(exePath, addIcon); RegisterFolderShellCommand(menuName, command, iconPath); } /// /// Регистрирует пункт меню для папок с заданной полной командой. /// public static void RegisterFolderShellCommand(string menuName, string command, string? iconPath = null) { 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, iconPath); } catch (Exception ex) { foreach (string keyPath in createdKeys) { try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { } } throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex); } } /// /// Удаляет пункт меню для папок. /// 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 = "", bool addIcon = false) { string appPath = Process.GetCurrentProcess().MainModule.FileName; RegisterFolderBackgroundShellForExe(menuName, appPath, commandPrefix, addIcon); } /// /// Регистрирует пункт меню для фона папки с заданным исполняемым файлом. /// public static void RegisterFolderBackgroundShellForExe(string menuName, string exePath, string commandPrefix = "", bool addIcon = false) { if (string.IsNullOrWhiteSpace(exePath)) throw new ArgumentException("Путь к исполняемому файлу не может быть пустым.", nameof(exePath)); commandPrefix ??= ""; string command = BuildCommand(exePath, commandPrefix, "\"%V\""); string? iconPath = GetIconPath(exePath, addIcon); RegisterFolderBackgroundShellCommand(menuName, command, iconPath); } /// /// Регистрирует пункт меню для фона папки с заданной полной командой. /// public static void RegisterFolderBackgroundShellCommand(string menuName, string command, string? iconPath = null) { 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, iconPath); } 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 }