Files
ContextMenuRegistrar/ContextMenuRegistrar.cs

430 lines
25 KiB
C#
Raw 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 void RegisterWithCommand(string rootPath, string menuName, string command, List<string> 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 Регистрация для файлов по расширениям
/// <summary>
/// Регистрирует пункт меню для указанных расширений файлов, используя текущее приложение.
/// </summary>
/// <param name="menuName">Имя пункта меню (отображается в контекстном меню).</param>
/// <param name="extensions">Коллекция расширений, например {".mp4", ".avi"}.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки, передаваемый перед именем файла.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или расширение.</exception>
/// <exception cref="ArgumentNullException">extensions равно null.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации (выполнен откат).</exception>
public static void RegisterFileExtensions(string menuName, IEnumerable<string> extensions, string commandPrefix = "")
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterFileExtensionsForExe(menuName, extensions, appPath, commandPrefix);
}
/// <summary>
/// Регистрирует пункт меню для указанных расширений файлов с заданным исполняемым файлом.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="extensions">Коллекция расширений.</param>
/// <param name="exePath">Полный путь к исполняемому файлу.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню, расширение или путь к exe.</exception>
/// <exception cref="ArgumentNullException">extensions равно null.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации (выполнен откат).</exception>
public static void RegisterFileExtensionsForExe(string menuName, IEnumerable<string> 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);
}
/// <summary>
/// Регистрирует пункт меню для указанных расширений файлов с заданной полной командой.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="extensions">Коллекция расширений.</param>
/// <param name="command">Полная командная строка, которая будет записана в ключ "command".</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню, расширение или пустая команда.</exception>
/// <exception cref="ArgumentNullException">extensions равно null.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации (выполнен откат).</exception>
public static void RegisterFileExtensionsCommand(string menuName, IEnumerable<string> 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<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);
}
}
catch (Exception ex)
{
// Откат: удаляем созданные ключи
foreach (string keyPath in createdKeys)
{
try { Registry.ClassesRoot.DeleteSubKeyTree(keyPath, false); } catch { }
}
throw new InvalidOperationException($"Ошибка при регистрации: {ex.Message}", ex);
}
}
/// <summary>
/// Удаляет ранее зарегистрированный пункт меню для указанных расширений файлов.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="extensions">Коллекция расширений.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или расширение.</exception>
/// <exception cref="ArgumentNullException">extensions равно null.</exception>
/// <exception cref="InvalidOperationException">Не удалось удалить одно или несколько расширений.</exception>
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>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
public static void RegisterAllFiles(string menuName, string commandPrefix = "")
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterAllFilesForExe(menuName, appPath, commandPrefix);
}
/// <summary>
/// Регистрирует пункт меню для всех файлов с заданным исполняемым файлом.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="exePath">Полный путь к исполняемому файлу.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или путь к exe.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
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);
}
/// <summary>
/// Регистрирует пункт меню для всех файлов с заданной полной командой.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="command">Полная командная строка, которая будет записана в ключ "command".</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или пустая команда.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
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<string> 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);
}
}
/// <summary>
/// Удаляет пункт меню для всех файлов.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню.</exception>
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>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
public static void RegisterFolderShell(string menuName, string commandPrefix = "")
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterFolderShellForExe(menuName, appPath, commandPrefix);
}
/// <summary>
/// Регистрирует пункт меню для папок с заданным исполняемым файлом.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="exePath">Полный путь к исполняемому файлу.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или путь к exe.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
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);
}
/// <summary>
/// Регистрирует пункт меню для папок с заданной полной командой.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="command">Полная командная строка, которая будет записана в ключ "command".</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или пустая команда.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
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<string> 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);
}
}
/// <summary>
/// Удаляет пункт меню для папок (из раздела Shell).
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню.</exception>
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>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
public static void RegisterFolderBackgroundShell(string menuName, string commandPrefix = "")
{
string appPath = Process.GetCurrentProcess().MainModule.FileName;
RegisterFolderBackgroundShellForExe(menuName, appPath, commandPrefix);
}
/// <summary>
/// Регистрирует пункт меню для фона папки с заданным исполняемым файлом.
/// В команде рекомендуется использовать параметр "%V" для передачи пути к текущей папке.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="exePath">Полный путь к исполняемому файлу.</param>
/// <param name="commandPrefix">Необязательный префикс командной строки.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или путь к exe.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
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);
}
/// <summary>
/// Регистрирует пункт меню для фона папки с заданной полной командой.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <param name="command">Полная командная строка, которая будет записана в ключ "command". Рекомендуется использовать "%V".</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню или пустая команда.</exception>
/// <exception cref="InvalidOperationException">Ошибка при регистрации.</exception>
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<string> 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);
}
}
/// <summary>
/// Удаляет пункт меню для фона папки.
/// </summary>
/// <param name="menuName">Имя пункта меню.</param>
/// <exception cref="UnauthorizedAccessException">Приложение не запущено от имени администратора.</exception>
/// <exception cref="ArgumentException">Некорректное имя меню.</exception>
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
}