5

C#不提升自己程序的权限实现操作注册表 - Aidan_Lee

 1 year ago
source link: https://www.cnblogs.com/AidanLee/p/16962520.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

当我们编写了自己的C#程序,有程序自定义的文件类型时,通常希望它满足以下需求:

  • 双击自定义文件打开自定义程序
  • 自定义文件有着自己的图标

此时,在网上检索可以发现,大多数回答是使用Microsoft.Win32下的CreateSubKey(String)函数,但是很不幸,Win10的注册表项受访问控制列表(ACL)保护。特别是想要实现上述两个需求时,写入HKEY_CLASSES_ROOT,程序会报错。

2605793-20221207111307846-198301100.png

这时,我们可能会进一步提高程序运行时的权限,例如,使用管理员权限启动程序,并且有各种各样的方式。虽然可行,但是某些情况下,是需要以非管理员权限执行的,这时又要降级,没有必要的反反复复,着实累!而且会使得用户心存疑虑[1]

受到HandyControl源码启发,可以使用以下的方式,实现上述两个需求,同时不用提升自己程序的运行权限,从而免去了一系列的麻烦。


2. 主体思路

查看它的源码,一言以蔽之:利用CMD,执行注册表reg文件,实现读写删注册表。具体的实现步骤为:

  1. 获取当前程序主模块的路径
  2. 检测路径下是否存在reg文件,如果有,则退出;如果没有,则认为是第一次启动,注册表内没有写入想要的信息,继续执行以下步骤
  3. 读取准备的txt文件(含操作注册表的内容)
  4. 将txt中操作注册表内容的参数,根据需要替换赋值
  5. 写入reg文件
  6. cmd执行reg文件,自动弹出管理权限获取窗口
  7. “是”实现reg文件操作,“否”取消操作

但是上述步骤可以考虑以下的优化方向:

  • 执行reg文件前,询问用户是否可以写入注册表以实现双击打开文件功能,变得用户友好型
  • 若用户点击否,该功能则再也无法出现。用户想要实现双击打开文件功能,无从下手
  • 若目录下的reg被删除,该功能再次出现,即使注册表中已写入信息

因此,我们在此优化为如图所示的步骤:

2605793-20221207200117914-1750623079.png

3. C#实现

在程序属性中,指定好使用的 ico 文件。

2605793-20221207201858360-1644399454.png

假设自定义的文件后缀名为:.mySuffix。那么实现上述两个需求,按照Saito Asuka
的步骤可以手动实现。结合注册表文件编写方法,利用优化后的流程,即可程序实现。

3.1 检测是否注册

函数Registry.ClassesRoot.OpenSubKey(".mySuffix")可以读取其中的名称,如果没有,返回null,实现变相的判断是否存在。对于同一个后缀名,可能有着不同的程序实现,需要遍历所有的值。在关联的值中,查看是否有 open/command 的值。Registry.ClassesRoot.OpenSubKey(path).GetValue(null)可以返回名称对应的值。

点击查看代码

private bool IsRegistryExist(string suffix, string path) { try { using RegistryKey hkSoftWare = Registry.ClassesRoot.OpenSubKey(suffix); if (hkSoftWare == null) return false; // 获取到该项下所有的名称 string[] sValueNameColl = hkSoftWare.GetValueNames(); int len = sValueNameColl.Length; // 获取到所有名称对应的数据 for (int i = 0; i < len; i++) { string data = hkSoftWare.GetValue(sValueNameColl[i]).ToString(); if (string.Equals(data, string.Empty)) continue; RegistryKey rk = Registry.ClassesRoot.OpenSubKey($"{data}\\shell\\open\\command"); if (rk == null) continue; var commandData = rk.GetValue(null)?.ToString(); rk.Close(); if (commandData == null) return false; if (string.Equals(commandData, string.Empty)) return false; if (string.Equals(commandData, path)) return true; } } finally { } return false; }

3.2 替换参数写入reg文件并执行

本程序基于 WPF ,因此获取程序所在路径使用的是Process.GetCurrentProcess().MainModule在替换时,务必注意先后顺序,想知道不按顺序的后果,自己可以试验一下😀。

点击查看代码

private void UpdateRegistry() { // 获取程序运行路径 var processModule = Process.GetCurrentProcess().MainModule; if (processModule == null) return; if (IsRegistryExist(".mySuffix", processModule.FileName)) return; var processWithSuffix = processModule.ModuleName.Split('.')[0] + ".mySuffix"; var registryFilePath = $"{Path.GetDirectoryName(processModule.FileName)}\\Registry.reg"; if (!File.Exists(registryFilePath)) { string registryStr = "Windows Registry Editor Version 5.00\r\n" + "\r\n" + "[HKEY_CLASSES_ROOT\\##]\r\n" + "@=\"###\"\r\n" + "\r\n" + "[HKEY_CLASSES_ROOT\\###\\DefaultIcon]\r\n" + "@=\"#\"\r\n" + "\r\n" + "[HKEY_CLASSES_ROOT\\###\\shell]\r\n" + "\r\n" + "[HKEY_CLASSES_ROOT\\###\\shell\\open]\r\n" + "\r\n" + "[HKEY_CLASSES_ROOT\\###\\shell\\open\\command]\r\n" + "@=\"#\"" + "\r\n"; // 替换 var newRegistryStr = registryStr.Replace("###", processWithSuffix).Replace("##", ".mySuffix").Replace("#", processModule.FileName.Replace("\\", "\\\\")); File.WriteAllText(registryFilePath, newRegistryStr); } Process.Start(new ProcessStartInfo("cmd", $"/c {registryFilePath}") { UseShellExecute = false, CreateNoWindow = true }); }

3.3 更新注册表

想要实现程序第一次启动后,仅注册写入一次,将上述函数,放置在OnStartup(StartupEventArgs e)函数中。

protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); UpdateRegistry(); }

在桌面新建自定义文件后,可以实现自定义的 ico 图标(与第3节中,程序指定的ico文件图标相同),双击后,也可以实现打开自定义程序。

image


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK