在没有管理员权限的情况下注册 COM
Registering a COM without Admin rights
我想将 .net 程序集注册为 COM。
事实上,据我所知,这意味着我希望将条目写入 HKEY_CURRENT_USER/Software/Classes
而不是 HKEY_CLASSES_ROOT
,因此不需要 UAC/Admin 权限.
找到了该问题的两个解决方案,我都在努力:
1) 编程方式,代码如下:
IntPtr key;
var openKeyresult = RegOpenKeyEx(HKEY_CURRENT_USER, "SOFTWARE\Classes", 0, (int)RegistrySecurity.KEY_WOW64_64KEY, out key);
var overrideKeyResult = RegOverridePredefKey(HKEY_CLASSES_ROOT, key);
var registerResult = Registrar.RegisterAssembly(GetAssembly(), AssemblyRegistrationFlags.SetCodeBase);
在这种方法中,overrideKeyResult
是 6,对应于 ERROR_INVALID_HANDLE,因此,RegisterAssembly
抛出一个 "access denied" 异常,因为它试图写入 HKEY_CLASSES_ROOT
.
旁注:每次我 运行 RegOpenKeyEx
键值都不一样,可以吗?
2) 有重气
通过使用带有 /regfile
标志的 regasm.exe
,然后在生成的 .reg
文件中将所有 HKEY_CLASSES_ROOT
出现的地方替换为 HKEY_CURRENT_USER/Software/Classes
我认为这应该可行,但是当我不想卸载 Outlook AddIn 时如何注销此类程序集?
据我所知,我无法以与注册相同的方式进行注册,因为:
var openKeyresult = RegOpenKeyEx(HKEY_CURRENT_USER, "SOFTWARE\Classes", 0, (int)RegistrySecurity.KEY_WOW64_64KEY, out key);
通过手动将所需的注册表项添加到 HKLM
解决了这个问题,事实证明这并不难,而且 regasm.exe
或 Registrar.RegisterAssembly
并没有那么多(在至少在涉及到我在没有管理员权限的情况下注册 Outlook 插件的用例时)
为了它的价值,我编写了一组 C# 实用程序,在用户注册表中 register/unregister 一个 .NET 类型(当然应该标记为 ComVisible),不需要 regasm,也不需要 UAC 提示,你可以这样使用:
// register into current user registry, needs no specific rights
ComUtilities.RegisterComObject(ComUtilities.Target.User, typeof(MyClass));
// unregister from user registry, needs no specific rights
ComUtilities.UnregisterComObject(ComUtilities.Target.User, typeof(MyClass));
// register into machine registry (needs admin, UAC, etc.)
ComUtilities.RegisterComObject(ComUtilities.Target.Machine, typeof(MyClass));
// unregister from machine registry (needs admin, UAC, etc.)
ComUtilities.UnregisterComObject(ComUtilities.Target.Machine, typeof(MyClass));
public static class ComUtilities
{
private const string ClsidRegistryKey = @"Software\Classes\CLSID";
public enum Target
{
Machine, // registers or unregisters a .NET COM object in HKEY_LOCAL_MACHINE, for all users, needs proper rights
User // registers or unregisters a .NET COM object in HKEY_CURRENT_USER to avoid UAC prompts
}
public static void RegisterComObject(Target target, Type type)
{
RegisterComObject(target, type, null);
}
public static void RegisterComObject(Target target, Type type, string assemblyPath)
{
RegisterComObject(target, type, assemblyPath, null);
}
public static void RegisterComObject(Target target, Type type, string assemblyPath, string runtimeVersion)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (type.Assembly == null)
throw new ArgumentException(null, nameof(type));
// note we don't check if the type is marked as ComVisible, maybe we should
if (assemblyPath == null)
{
assemblyPath = new Uri(type.Assembly.Location).LocalPath;
}
if (runtimeVersion == null)
{
runtimeVersion = GetRuntimeVersion(type.Assembly);
}
var root = target == Target.User ? Registry.CurrentUser : Registry.LocalMachine;
using (RegistryKey key = EnsureSubKey(root, Path.Combine(ClsidRegistryKey, type.GUID.ToString("B"), "InprocServer32")))
{
key.SetValue(null, "mscoree.dll");
key.SetValue("Assembly", type.Assembly.FullName);
key.SetValue("Class", type.FullName);
key.SetValue("ThreadingModel", "Both");
if (assemblyPath != null)
{
key.SetValue("CodeBase", assemblyPath);
}
key.SetValue("RuntimeVersion", runtimeVersion);
}
using (RegistryKey key = EnsureSubKey(root, Path.Combine(ClsidRegistryKey, type.GUID.ToString("B"))))
{
// cf
using (RegistryKey cats = EnsureSubKey(key, @"Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"))
{
// do nothing special
}
var att = type.GetCustomAttribute<ProgIdAttribute>();
if (att != null && !string.IsNullOrEmpty(att.Value))
{
using (RegistryKey progid = EnsureSubKey(key, "ProgId"))
{
progid.SetValue(null, att.Value);
}
}
}
}
public static void UnregisterComObject(Target target, Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
var root = target == Target.User ? Registry.CurrentUser : Registry.LocalMachine;
using (RegistryKey key = root.OpenSubKey(ClsidRegistryKey, true))
{
if (key == null)
return;
key.DeleteSubKeyTree(type.GUID.ToString("B"), false);
}
}
// kind of hack to determine clr version of an assembly
private static string GetRuntimeVersion(Assembly asm)
{
string def = "v4.0.30319"; // use CLR4 as the default
try
{
var mscorlib = asm.GetReferencedAssemblies().FirstOrDefault(a => a.Name == "mscorlib");
if (mscorlib != null && mscorlib.Version.Major < 4)
return "v2.0.50727"; // use CLR2
}
catch
{
// too bad, assume CLR4
}
return def;
}
private static RegistryKey EnsureSubKey(RegistryKey root, string name)
{
RegistryKey key = root.OpenSubKey(name, true);
if (key != null)
return key;
string parentName = Path.GetDirectoryName(name);
if (string.IsNullOrEmpty(parentName))
return root.CreateSubKey(name);
using (RegistryKey parentKey = EnsureSubKey(root, parentName))
{
return parentKey.CreateSubKey(Path.GetFileName(name));
}
}
}
我想将 .net 程序集注册为 COM。
事实上,据我所知,这意味着我希望将条目写入 HKEY_CURRENT_USER/Software/Classes
而不是 HKEY_CLASSES_ROOT
,因此不需要 UAC/Admin 权限.
找到了该问题的两个解决方案,我都在努力:
1) 编程方式,代码如下:
IntPtr key;
var openKeyresult = RegOpenKeyEx(HKEY_CURRENT_USER, "SOFTWARE\Classes", 0, (int)RegistrySecurity.KEY_WOW64_64KEY, out key);
var overrideKeyResult = RegOverridePredefKey(HKEY_CLASSES_ROOT, key);
var registerResult = Registrar.RegisterAssembly(GetAssembly(), AssemblyRegistrationFlags.SetCodeBase);
在这种方法中,overrideKeyResult
是 6,对应于 ERROR_INVALID_HANDLE,因此,RegisterAssembly
抛出一个 "access denied" 异常,因为它试图写入 HKEY_CLASSES_ROOT
.
旁注:每次我 运行 RegOpenKeyEx
键值都不一样,可以吗?
2) 有重气
通过使用带有 /regfile
标志的 regasm.exe
,然后在生成的 .reg
文件中将所有 HKEY_CLASSES_ROOT
出现的地方替换为 HKEY_CURRENT_USER/Software/Classes
我认为这应该可行,但是当我不想卸载 Outlook AddIn 时如何注销此类程序集?
据我所知,我无法以与注册相同的方式进行注册,因为:
var openKeyresult = RegOpenKeyEx(HKEY_CURRENT_USER, "SOFTWARE\Classes", 0, (int)RegistrySecurity.KEY_WOW64_64KEY, out key);
通过手动将所需的注册表项添加到 HKLM
解决了这个问题,事实证明这并不难,而且 regasm.exe
或 Registrar.RegisterAssembly
并没有那么多(在至少在涉及到我在没有管理员权限的情况下注册 Outlook 插件的用例时)
为了它的价值,我编写了一组 C# 实用程序,在用户注册表中 register/unregister 一个 .NET 类型(当然应该标记为 ComVisible),不需要 regasm,也不需要 UAC 提示,你可以这样使用:
// register into current user registry, needs no specific rights
ComUtilities.RegisterComObject(ComUtilities.Target.User, typeof(MyClass));
// unregister from user registry, needs no specific rights
ComUtilities.UnregisterComObject(ComUtilities.Target.User, typeof(MyClass));
// register into machine registry (needs admin, UAC, etc.)
ComUtilities.RegisterComObject(ComUtilities.Target.Machine, typeof(MyClass));
// unregister from machine registry (needs admin, UAC, etc.)
ComUtilities.UnregisterComObject(ComUtilities.Target.Machine, typeof(MyClass));
public static class ComUtilities
{
private const string ClsidRegistryKey = @"Software\Classes\CLSID";
public enum Target
{
Machine, // registers or unregisters a .NET COM object in HKEY_LOCAL_MACHINE, for all users, needs proper rights
User // registers or unregisters a .NET COM object in HKEY_CURRENT_USER to avoid UAC prompts
}
public static void RegisterComObject(Target target, Type type)
{
RegisterComObject(target, type, null);
}
public static void RegisterComObject(Target target, Type type, string assemblyPath)
{
RegisterComObject(target, type, assemblyPath, null);
}
public static void RegisterComObject(Target target, Type type, string assemblyPath, string runtimeVersion)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (type.Assembly == null)
throw new ArgumentException(null, nameof(type));
// note we don't check if the type is marked as ComVisible, maybe we should
if (assemblyPath == null)
{
assemblyPath = new Uri(type.Assembly.Location).LocalPath;
}
if (runtimeVersion == null)
{
runtimeVersion = GetRuntimeVersion(type.Assembly);
}
var root = target == Target.User ? Registry.CurrentUser : Registry.LocalMachine;
using (RegistryKey key = EnsureSubKey(root, Path.Combine(ClsidRegistryKey, type.GUID.ToString("B"), "InprocServer32")))
{
key.SetValue(null, "mscoree.dll");
key.SetValue("Assembly", type.Assembly.FullName);
key.SetValue("Class", type.FullName);
key.SetValue("ThreadingModel", "Both");
if (assemblyPath != null)
{
key.SetValue("CodeBase", assemblyPath);
}
key.SetValue("RuntimeVersion", runtimeVersion);
}
using (RegistryKey key = EnsureSubKey(root, Path.Combine(ClsidRegistryKey, type.GUID.ToString("B"))))
{
// cf
using (RegistryKey cats = EnsureSubKey(key, @"Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"))
{
// do nothing special
}
var att = type.GetCustomAttribute<ProgIdAttribute>();
if (att != null && !string.IsNullOrEmpty(att.Value))
{
using (RegistryKey progid = EnsureSubKey(key, "ProgId"))
{
progid.SetValue(null, att.Value);
}
}
}
}
public static void UnregisterComObject(Target target, Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
var root = target == Target.User ? Registry.CurrentUser : Registry.LocalMachine;
using (RegistryKey key = root.OpenSubKey(ClsidRegistryKey, true))
{
if (key == null)
return;
key.DeleteSubKeyTree(type.GUID.ToString("B"), false);
}
}
// kind of hack to determine clr version of an assembly
private static string GetRuntimeVersion(Assembly asm)
{
string def = "v4.0.30319"; // use CLR4 as the default
try
{
var mscorlib = asm.GetReferencedAssemblies().FirstOrDefault(a => a.Name == "mscorlib");
if (mscorlib != null && mscorlib.Version.Major < 4)
return "v2.0.50727"; // use CLR2
}
catch
{
// too bad, assume CLR4
}
return def;
}
private static RegistryKey EnsureSubKey(RegistryKey root, string name)
{
RegistryKey key = root.OpenSubKey(name, true);
if (key != null)
return key;
string parentName = Path.GetDirectoryName(name);
if (string.IsNullOrEmpty(parentName))
return root.CreateSubKey(name);
using (RegistryKey parentKey = EnsureSubKey(root, parentName))
{
return parentKey.CreateSubKey(Path.GetFileName(name));
}
}
}