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
}