You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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原生的注册表配置,避免自定义代码的上下文问题,步骤如下:

  1. 在Installer-SetupProject中右键选择View > Registry
  2. 导航到HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Excel\Options,若路径不存在则手动创建各级子项
  3. 添加字符串值:
    • 名称:OPEN(多个加载项可依次命名为OPEN1、OPEN2等)
    • 值:[TARGETDIR]package.xll([TARGETDIR]是MSI内置变量,会自动替换为实际安装目录)
  4. 如需支持多版本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,保证代码以当前用户身份执行。


验证步骤

安装完成后:

  1. 打开注册表编辑器,检查HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Excel\Options下是否存在目标OPEN键
  2. 启动Excel,通过文件>选项>加载项确认XLL是否自动加载

内容的提问来源于stack exchange,提问作者Hưng Nguyễn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 21:24:50