如何在Visual安装项目中通过CustomAction正确写入HKCU注册表项?
ExcelDna XLL自动加载的MSI安装注册表写入问题解决方案
问题背景
我的解决方案包含以下项目:
- 运行正常的VSTO .NET Framework 4.8.1项目
- 为Excel扩展工具函数(如SumByStyle)的ExcelDna项目,已生成32位和64位package.xll文件,手动加载可正常工作
- 基于Microsoft Visual Studio Installer Projects 2022 v3.0.0的Installer-SetupProject打包项目
为实现安装后无需用户操作自动加载XLL,我添加了C#类库项目,实现继承自Installer的类并覆盖安装、卸载方法,将其指定为安装项目的CustomAction。核心注册表注册代码如下:
public void RegisterOfficeExcelExtension() { WriteLog("Start RegisterOfficeExcelExtension"); LogCurrentContext(); using (RegistryKey officeRoot = Registry.CurrentUser.OpenSubKey(@"Software\\Microsoft\\Office", writable: true)) { if (officeRoot == null) { WriteLog("Not found Office in HKCU"); return; } foreach (string version in officeRoot.GetSubKeyNames()) { if (!Regex.IsMatch(version, @"^\d+(\.\d+)?$")) continue; using (RegistryKey versionKey = officeRoot.OpenSubKey(version, writable: true)) { if (versionKey == null) continue; using (RegistryKey excelOptions = versionKey.CreateSubKey(@"Excel\\Options")) { if (excelOptions == null) continue; var existingValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string valueName in excelOptions.GetValueNames()) { existingValues.Add(valueName); } int index = 0; foreach (string file in fullFilePathExt) { bool alreadyRegistered = false; foreach (string valueName in excelOptions.GetValueNames()) { string strValue = excelOptions.GetValue(valueName)?.ToString()?.Trim('\"'); if (string.Equals(strValue, file, StringComparison.OrdinalIgnoreCase)) { alreadyRegistered = true; WriteLog($"File: {file} Key: {valueName}"); break; } } if (!alreadyRegistered) { string newKeyName; do { newKeyName = index == 0 ? "OPEN" : $"OPEN{index}"; index++; } while (existingValues.Contains(newKeyName)); excelOptions.SetValue(newKeyName, file, RegistryValueKind.String); existingValues.Add(newKeyName); string regPath = $@"HKCU\\Software\\Microsoft\\Office\\{version}\\Excel\\Options"; WriteLog($"KeyCreated: '{newKeyName}' Value: '{file}' At: {regPath}"); } } } } } } WriteLog("End RegisterOfficeExcelExtension"); }
问题现象
日志显示注册表键已成功写入Computer\HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Excel\Options,但实际在该路径下无法找到,反而在Computer\HKEY_USERS\S-1-5-18\Software\Microsoft\Office\16.0\Excel\Options(系统内置账户的HKCU分支)中找到所有写入的键,推测MSI安装程序运行在非当前用户HKCU上下文导致写入错误。
可行解决方案
方案1:调整CustomAction的执行上下文
在Visual Studio安装项目中,找到对应的CustomAction,修改以下属性:
- InstallerClass:设为
True(确保你的Installer类已标记[RunInstaller(true)]特性) - Execute:改为
deferred - Impersonate:设为
True
此设置会让CustomAction以当前登录用户的身份执行,而非系统账户,确保注册表写入到当前用户的HKCU分支。
方案2:直接通过安装项目添加注册表项(推荐)
使用MSI原生的注册表配置,避免自定义代码的上下文问题,步骤如下:
- 在Installer-SetupProject中右键选择View > Registry
- 导航到
HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Excel\Options,若路径不存在则手动创建各级子项 - 添加字符串值:
- 名称:
OPEN(多个加载项可依次命名为OPEN1、OPEN2等) - 值:
[TARGETDIR]package.xll([TARGETDIR]是MSI内置变量,会自动替换为实际安装目录)
- 名称:
- 如需支持多版本Office,可复制该注册表项到对应版本的路径(如15.0、14.0),或通过MSI条件判断动态适配
方案3:修正CustomAction的注册表访问代码
若必须保留自定义Installer类,修改代码中注册表的访问方式,明确指向当前用户的注册表 hive:
public void RegisterOfficeExcelExtension() { WriteLog("Start RegisterOfficeExcelExtension"); LogCurrentContext(); // 替换Registry.CurrentUser为明确获取当前用户的注册表基项 var currentUserRegistry = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default); using (RegistryKey officeRoot = currentUserRegistry.OpenSubKey(@"Software\Microsoft\Office", writable: true)) { if (officeRoot == null) { WriteLog("Not found Office in HKCU"); return; } // 后续逻辑保持不变 foreach (string version in officeRoot.GetSubKeyNames()) { if (!Regex.IsMatch(version, @"^\d+(\.\d+)?$")) continue; using (RegistryKey versionKey = officeRoot.OpenSubKey(version, writable: true)) { if (versionKey == null) continue; using (RegistryKey excelOptions = versionKey.CreateSubKey(@"Excel\Options")) { if (excelOptions == null) continue; var existingValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string valueName in excelOptions.GetValueNames()) { existingValues.Add(valueName); } int index = 0; foreach (string file in fullFilePathExt) { bool alreadyRegistered = false; foreach (string valueName in excelOptions.GetValueNames()) { string strValue = excelOptions.GetValue(valueName)?.ToString()?.Trim('\"'); if (string.Equals(strValue, file, StringComparison.OrdinalIgnoreCase)) { alreadyRegistered = true; WriteLog($"File: {file} Key: {valueName}"); break; } } if (!alreadyRegistered) { string newKeyName; do { newKeyName = index == 0 ? "OPEN" : $"OPEN{index}"; index++; } while (existingValues.Contains(newKeyName)); excelOptions.SetValue(newKeyName, file, RegistryValueKind.String); existingValues.Add(newKeyName); string regPath = $@"HKCU\Software\Microsoft\Office\{version}\Excel\Options"; WriteLog($"KeyCreated: '{newKeyName}' Value: '{file}' At: {regPath}"); } } } } } } WriteLog("End RegisterOfficeExcelExtension"); }
同时确保CustomAction的Impersonate属性设为True,保证代码以当前用户身份执行。
验证步骤
安装完成后:
- 打开注册表编辑器,检查
HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Excel\Options下是否存在目标OPEN键 - 启动Excel,通过
文件>选项>加载项确认XLL是否自动加载
内容的提问来源于stack exchange,提问作者Hưng Nguyễn
相关产品推荐
相关产品推荐

