Files
ContextMenuRegistrar/ContextMenuRegistrar.cs
2026-02-23 14:59:07 +07:00

373 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Principal;
using Microsoft.Win32;
/// <summary>
/// Предоставляет методы для регистрации и удаления пунктов контекстного меню Windows.
/// </summary>
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<string> 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 Регистрация для файлов по расширениям
/// <summary>
/// Регистрирует пункт меню для указанных расширений файлов, используя текущее приложение.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="extensions">Коллекция расширений, например {".mp4", ".avi"}.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <param name="addIcon">Если true, в качестве иконки используется текущее приложение (индекс 0).</param>
public static void RegisterFileExtensions(string menuName, IEnumerable<string> extensions, string commandPrefix = "", bool addIcon = false)
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterFileExtensionsForExe(menuName, extensions, appPath, commandPrefix, addIcon);
}
/// <summary>
/// Регистрирует пункт меню для указанных расширений файлов с заданным исполняемым файлом.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="extensions">Коллекция расширений.</param>
/// <param name="exePath">Полный путь к исполняемому файлу.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <param name="addIcon">Если true, в качестве иконки используется указанный exePath (индекс 0).</param>
public static void RegisterFileExtensionsForExe(string menuName, IEnumerable<string> 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);
}
/// <summary>
/// Регистрирует пункт меню для указанных расширений файлов с заданной полной командой.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="extensions">Коллекция расширений.</param>
/// <param name="command">Полная командная строка.</param>
/// <param name="iconPath">Необязательный путь к иконке. Если не указан (null), иконка не добавляется.</param>
public static void RegisterFileExtensionsCommand(string menuName, IEnumerable<string> 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<string> 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);
}
}
/// <summary>
/// Удаляет ранее зарегистрированный пункт меню для указанных расширений файлов.
/// </summary>
public static void UnregisterFileExtensions(string menuName, IEnumerable<string> extensions)
{
CheckAdministrator();
if (string.IsNullOrWhiteSpace(menuName))
throw new ArgumentException("Имя пункта меню не может быть пустым.", nameof(menuName));
if (extensions == null)
throw new ArgumentNullException(nameof(extensions));
List<string> 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 Регистрация для всех файлов
/// <summary>
/// Регистрирует пункт меню для всех файлов, используя текущее приложение.
/// </summary>
public static void RegisterAllFiles(string menuName, string commandPrefix = "", bool addIcon = false)
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterAllFilesForExe(menuName, appPath, commandPrefix, addIcon);
}
/// <summary>
/// Регистрирует пункт меню для всех файлов с заданным исполняемым файлом.
/// </summary>
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);
}
/// <summary>
/// Регистрирует пункт меню для всех файлов с заданной полной командой.
/// </summary>
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<string> 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);
}
}
/// <summary>
/// Удаляет пункт меню для всех файлов.
/// </summary>
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 Регистрация для папок (щелчок на иконке папки)
/// <summary>
/// Регистрирует пункт меню для папок, используя текущее приложение.
/// </summary>
public static void RegisterFolderShell(string menuName, string commandPrefix = "", bool addIcon = false)
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterFolderShellForExe(menuName, appPath, commandPrefix, addIcon);
}
/// <summary>
/// Регистрирует пункт меню для папок с заданным исполняемым файлом.
/// </summary>
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);
}
/// <summary>
/// Регистрирует пункт меню для папок с заданной полной командой.
/// </summary>
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<string> 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);
}
}
/// <summary>
/// Удаляет пункт меню для папок.
/// </summary>
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 Регистрация для фона папки
/// <summary>
/// Регистрирует пункт меню для фона папки, используя текущее приложение.
/// В команде рекомендуется использовать "%V".
/// </summary>
public static void RegisterFolderBackgroundShell(string menuName, string commandPrefix = "", bool addIcon = false)
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterFolderBackgroundShellForExe(menuName, appPath, commandPrefix, addIcon);
}
/// <summary>
/// Регистрирует пункт меню для фона папки с заданным исполняемым файлом.
/// </summary>
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);
}
/// <summary>
/// Регистрирует пункт меню для фона папки с заданной полной командой.
/// </summary>
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<string> 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);
}
}
/// <summary>
/// Удаляет пункт меню для фона папки.
/// </summary>
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
}