原文:zz将 VSTO 插件部署给所有用户

  注:本文原作者 Misha Shneerson 是 VSTO 团队的工程师。原文可以在下列地址找到:http://blogs.msdn.com/mshneer/archive/2007/09/05/deploying-your-vsto-add-in-to-all-users-part-i.aspx

  VSTO 插件(也叫“托管代码 Office 插件”)在部署方面有一个严重的不足,简单来说,微软只告诉了我们如何把这些插件部署到单个用户环境;而“如何一次性部署到一台机器的所有用户环境中”成为了我们的阿喀琉斯之踵。本文将给告诉你如何解决这个问题。

  首先让我们了解一些背景知识。

  Office 2007 已经内置了对托管代码插件的支持,通过一个叫“Manifest”的注册表值,Office 应用程序可以区分传统的 COM 插件和托管代码插件。这个键值可以在下列位置找到:HKCU\Software\Microsoft\Office\<App> \AddIns\<AddInName>。

  传统的 COM 插件可以通过在 HKLM\Software\Microsoft\Office\<App>\AddIns 注册自己的信息从而实现让本机所有的用户都可以使用。但是托管代码插件只能注册在 HKCU 里面,注册在 HKLM 里面的托管代码插件会被忽略。

  要创建一个安装包,做到能够往启动安装程序的那个用户的注册表里面写入 插件信息并不困难。但是,要把同样的信息复制到本机所有用户的注册表键里面,就需要比较高级的 Win32 API 使用技巧了。如果还要再稍微深入一步,我们可以让安装程序把插件注册信息写入“模板”(存在于 C:\Documents and Settings\Default User\ntuser.dat),这样可以保证将来使用本机的新用户直接就有相应的注册表信息。关于这种技巧,请参见 Raymond Chen 的文章

  已经在抓脑袋了?其实有一种相对来说容易得多的解决方案。诀窍就在于使用 Office 的一个内部机制,在用户启动 Office 应用程序的时候,把 HKLM 里面注册的信息复制到当前用户的 HKCU 里面去。

  针对 Office 2007,这个神奇的键值就在这里:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings。如果你的电脑已经安装了 Office 2007,你可以打开注册表编辑器察看一下那里(如果是 x64 的操作系统,则应该在 Wow6432Node 的相应位置里面)。你会发现里面有不少看似随机的键,这些随机的键里面,又有“Create”以及“Delete”键;再进入一层看,就会发现里面似乎包 含着某些完整路径的注册表信息!

  我们所见到的就是 Office 2007 的一种 HKLM 到 HKCU 的信息复制机制,而这种机制可以在注册表中进行完全的控制。让我们来进一步研究这种机制到底是怎么工作的。我们可以先做一个小练习——把下面的内容复制到 记事本程序,存为“testpropagation_create.reg”文件。然后运行这个文件,把其中的信息导入 HKLM。

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\TestPropagation]
"Count"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\TestPropagation\Create\Software\Microsoft\Office\TestKey]
"TestValue"="Test"

  然后启动 Excel 2007,再检查一下注册表,你会发现你的 HKCU 中,有两个新的键值被创建出来了:

  • HKCU\Software\Microsoft\Office\TestKey 有了一个值:TestValue
  • HKCU\Software\Microsoft\Office\12.0\User Settings\TestPropagation 有一个 Count 值,并且等于 1

  下一步,我们看看如何用同样的机制来删除键值。把下面的内容复制并保存到“testpropagation_delete.reg”文件,并且运行:

Windows Registry Editor Version 5.00
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\TestPropagation\Create]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\TestPropagation]
"Count"=dword:00000002
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\TestPropagation\Delete\Software\Microsoft\Office\TestKey]

  高亮显示的文字标明了和之前一个文件不同的地方。我们先移除之前创建的 TestPropagation\Create 键,注意前面的“-”符号,它表示删除键值。为了让指令正确执行,我们需要保证 HKLM 里面的 Count 值和 HKCU 里面的 Count 值不同,所以把 Count 的值设置成了 2。最后,Software\Microsoft\Office\TestKey 被放在了 TestPropagation\Delete 里面,这样 Office 就知道应该是删除那个键值,而不是创建。

  执行了 testpropagation_delete.reg 之后,你会发现:

  • HKCU\Software\Microsoft\Office\TestKey 键已经被删除
  • HKCU\Software\Microsoft\Office\12.0\User Settings\TestPropagation 键的 Count 值被设置成了 2

  好了,现在我们已经清楚了 Office 的注册表键值复制机制是怎么工作的。至于怎么利用这种机制来改进我们的插件安装程序,应该也能猜个八九不离十了吧?在本文的下篇中,我们将一起来看如何为 一个 VSTO 2005 SE 的插件安装程序做一个自定义动作,结合上述机制,达到“部署到本机所有用户”的最终目的。

  在“上篇”中,我们介绍了准备知识。本文要把它付诸实用:如何在你的 Office 2007 插件安装程序中具体实现“部署到所有本机所有用户”,这包括任何安装程序都要处置的“安装”、“修复”和“删除”三大功能:

  • 在安装插件的时候,我们需要写入一些类似“上篇”中“testpropagation_create.reg”文件的注册表键值,其中包含的“Create”指令会让 Office 把注册表键值复制到用户的 HKCU 里面去。
  • 在卸载插件的时候,我们要把“Create”指令替换成“Delete”指令,让 Office 删除 HKCU 中对应的键值。同时,我们还需要把“Count”注册表值减少,这样可以保证 Office 要执行“Delete”指令。
  • 在执行修复的时候,我们需要增加“Count”的值,这样会让 Office 再把 HKLM 里面的键值复制到 HKCU。

  我们先来处理安装的时候需要用到的键值。最方便的方法莫过于修改一下现 成的 VSTO 2005 SE 插件安装程序工程(创建插件工程时附带自动创建的安装程序工程)。就让我们来开一个新的 Excel 2007 插件工程好了,起个名字叫“MyAddIn”。你会发现 VSTO 自动帮你创建了一个配套的 MyAddInSetup 工程。打开这个工程,切换到注册表视图,然后把里面现成的注册表键值设置全部删除(比如那些在 HKEY_CURRENT_USER 里面的)。然后,把下面的注册表信息保存到一个 .reg 文件中,右键单击“Registry on Target Machine”节点,选择“导入(Import)……”,选中你刚才创建的 .reg 文件。这样就把我们需要的注册表信息导入安装程序工程了。如果有必要,你可以调整下面高亮显示的部分,以对应项目的实际情况。

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MyCompany.MyAddIn\Create\Software\Microsoft\Office\Excel\Addins\MyCompany.MyAddIn]

"Description"="MyAddIn -- an addin created with VSTO technology"
"Manifest"="[TARGETDIR]MyAddIn.dll.manifest"
"FriendlyName"="MyAddIn"
"LoadBehavior"=dword:00000003
"CommandLineSafe"=dword:00000001

  做完这一步,你的注册表视图看起来应该类似于下图:

  好了,这样一来,我们就可以保证在卸载 Add-In 的时候,相关的注册表键值会被正确删除。我还建议大家在属性窗口里面把“Create”节点的“DeleteOnUninstall”属性设置成 True,以此保证这个键值会在卸载的时候被删除。

  请注意,我们还需要在“MyCompany.MyAddIn”节点增加一个“Count”值。但是不能通过“注册表视图”来加。因为我们不但需要在卸载的时候保留这个值,还要将其减小。如果在设计器里面添加,那就会在卸载的时候被删除了。

  所以我们需要创建一个自定义安装动作(Custom Action),让它来改变 Count 的值,并且在卸载过程中创建一个“Delete”指令。

  下面我将向大家展示 Darryn Lavery(促成本文的首要功臣)为他的 MSDN 专栏文章编写的代码(这篇即将发布的文章更加详尽地阐述了本文讨论的问题和解决方案),他无私地把代码与我共享,而我就借花献佛,拿来和大家分享。不过还 是要感谢 Darryn。

  现在我们就来仔细瞧瞧这个 Custom Action 的代码:先定义一个 RegisterOffice2007AddIn 类,添加一些私有方法。

class RegisterOffice2007AddIn {

#region private methods

private const string userSettingsLocation = @"Software\Microsoft\Office\12.0\User Settings";

public void IncrementCounter(RegistryKey instructionKey) {
        int count = 1;
        object value = instructionKey.GetValue("Count");

if (value != null) {
            if ((int)value != Int32.MaxValue)
                count = (int)value + 1;
        }

instructionKey.SetValue("Count", count);
    }

private string GetApplicationPath(string applicationName) {
        switch (applicationName.ToLower()) {
            case "excel":
                return @"Software\Microsoft\Office\Excel\Addins\";

case "infopath":
                return @"Software\Microsoft\Office\InfoPath\Addins\";

case "outlook":
                return @"Software\Microsoft\Office\Outlook\Addins\";

case "powerpoint":
                return @"Software\Microsoft\Office\PowerPoint\Addins\";

case "word":
                return @"Software\Microsoft\Office\Word\Addins\";

case "visio":
                return @"Software\Microsoft\Visio\Addins\";

case "project":
                return @"Software\Microsoft\Office\MS Project\Addins\";

default:
                throw new Exception(applicationName + " is not a supported application", null);
        }
    }

#endregion

  上面的代码有两个方法:IncrementCounter 负责正确地更新“Count”键值;GetApplicationPath 返回针对某个 Office 应用程序的插件信息注册表键值路径。

  再看一个在“安装”和“修复”的时候直接被调用的方法:

public void RegisterAddIn(string addInName) {
    RegistryKey userSettingsKey = null;
    RegistryKey instructionKey = null;

try {
        userSettingsKey = Registry.LocalMachine.OpenSubKey(userSettingsLocation, true);

if (userSettingsKey == null) {
            throw new Exception("Internal error: Office User Settings key does not exist", null);
        }

instructionKey = userSettingsKey.OpenSubKey(addInName, true);

if (instructionKey == null) {
            instructionKey = userSettingsKey.CreateSubKey(addInName);
        } else {
            // 删除“Delete”指令
            try {
                instructionKey.DeleteSubKeyTree("DELETE");
            } catch (ArgumentException) { } // “Delete”指令不存在,忽略这种情况下的异常
        }

IncrementCounter(instructionKey);

} finally {
        if (instructionKey != null)
            instructionKey.Close();

if (userSettingsKey != null)
            userSettingsKey.Close();
    }
}

  在上面的代码中,我们首先保证“Delete”注册表指令不存在,然后增加计数器值(Count 键值)。要注意的是,我们没有用代码创建“Create”指令,因为这个指令已经在之前的“注册表视图”中定义好了,它会被自动写入注册表。

  最后就是在“卸载”的时候被调用的方法:

public void UnRegisterAddIn(string applicationName, string addInName) {
    RegistryKey userSettingsKey = null;
    RegistryKey instructionKey = null;
    RegistryKey deleteKey = null;

try {
        userSettingsKey = Registry.LocalMachine.OpenSubKey(userSettingsLocation, true);

if (userSettingsKey == null) {
            throw new Exception("Internal error: Office User Settings key does not exist", null);
        }

instructionKey = userSettingsKey.OpenSubKey(addInName, true);

if (instructionKey == null) {
            instructionKey = userSettingsKey.CreateSubKey(addInName);
        } else {
            // 确保“Create”指令被删除
            try {
                instructionKey.DeleteSubKeyTree("CREATE");
            } catch (ArgumentException) { } // “Create”指令不存在,忽略这种情况下的异常
        }

string instructionString =
                        @"DELETE\" +
                        GetApplicationPath(applicationName) +
                        @"\" +
                        addInName;

deleteKey = instructionKey.CreateSubKey(instructionString);

IncrementCounter(instructionKey);
    } finally {
        if (deleteKey != null)
            deleteKey.Close();

if (instructionKey != null)
            instructionKey.Close();

if (userSettingsKey != null)
            userSettingsKey.Close();
    }
}

  好了,差不多大功告成!只要把这些代码包装进一个 Custom Action DLL(假设你已经有类似的经验),把 CustomActionData 属性设置成 /addinName="MyCompany.MyAddIn" /application="Excel"(具体的值根据你的情况自定义)。在“安装”、“卸载”或者“回滚”的时候调用这些方法,就可以啦。我会在稍后的日志中作一个总结。

zz将 VSTO 插件部署给所有用户的更多相关文章

  1. 二进制部署K8S-3核心插件部署

    二进制部署K8S-3核心插件部署 5.1. CNI网络插件 kubernetes设计了网络模型,但是pod之间通信的具体实现交给了CNI往插件.常用的CNI网络插件有:Flannel .Calico. ...

  2. k8s之Dashboard插件部署及使用

    k8s之Dashboard插件部署及使用 目录 k8s之Dashboard插件部署及使用 1. Dashboard介绍 2. 服务器环境 3. 在K8S工具目录中创建dashboard工作目录 4. ...

  3. 场景实践:基于 IntelliJ IDEA 插件部署微服务应用

    体验简介 阿里云云起实验室提供相关实验资源,点击前往 本场景指导您把微服务应用部署到 SAE 平台: 登陆 SAE 控制台,基于 jar 包创建应用 基于 IntelliJ IDEA 插件更新 SAE ...

  4. eclipse maven项目中使用tomcat插件部署项目

    maven的tomcat插件部署web项目,我简单认为分两种,一种是部署到内置tomcat,另一种是部署到安装的tomcat. 第一种部署,默认是部署在内置tomcat的8080端口,如果不需要改端口 ...

  5. 插件部署【BE、BP、UI】

    1.BE插件部署 XML部署位置:D:\yonyou\U9V50\Portal\bin: DLL部署位置:D:\yonyou\U9V50\Portal\ApplicationServer\Libs; ...

  6. Elasticsearch 5.0 —— Head插件部署指南

    使用ES的基本都会使用过head,但是版本升级到5.0后,head插件就不好使了.下面就看看如何在5.0中启动Head插件吧! 官方粗略教程 Running with built in server ...

  7. VSTO安装部署(完美解决XP+2007)

    从开始写VSTO的插件开始,安装部署一直就是一个很大的难题,其实难题的原因主要是针对XP+2007而言.在Win7上,由于基本上都预装了.net framework,所以安装起来其实问题不大. 主要需 ...

  8. Elasticsearch 5.0 —— Head插件部署指南(Head目前支持5.0了!请不要看本篇文章了)

    使用ES的基本都会使用过head,但是版本升级到5.0后,head插件就不好使了.下面就看看如何在5.0中启动Head插件吧! Head目前支持5.0了!请不要看本篇文章了 Head目前支持5.0了! ...

  9. Jenkins Docker安装及Docker build step插件部署配置

    生产部署环境:A:192.168.1.2 B:192.168.1.3  两台服务器系统均是Centos 7.3 , Docker版本都1.12.6 Jenkins安装操作步骤: 1.在A服务器上使用命 ...

随机推荐

  1. android 12 click事件的不同实现方式

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layo ...

  2. java Map实现的cache manager

    一个模仿memcached的JAVA虚拟缓存工具,可以缓存java对象 import java.io.ByteArrayInputStream; import java.io.ByteArrayOut ...

  3. 了解ANSI编码

    ANSI:American National Standards Institute:美国国家标准学会 ANSI编码:为使计算机支持更多语言,不同国家和地区分别制定了符合自身的外文字符延伸编码方式(如 ...

  4. xslt语法之---基础语法

    1. XSLT常用元素: 1.1 <xsl:template>:创建模板     Match属性的作用是使模板和XML元素相关联 <xsl:template match=" ...

  5. Junit简介和常用API

    测试几个的概念 白盒测试——把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人员是公开的. 回归测试——软件或环境的修复或更正后的“再测试”,自动测试工具对这类测试尤其有用. 单元测试 ...

  6. angular应用前景

    完成了angularJs的学习,突然想到,angularJS是否会影响到seo.于是查阅了很多资料,技术博客,这种想法得到了证实. 爬虫不能识别js渲染的内容.所以引起了我对angular应用前景的思 ...

  7. Android Studio创建工程时一直卡在下载Gradle

    一直提示这个进度条,查了不少资料,有的说FQ,有的说下载gradle后运行下bin里面的批处理,再在环境变量里Path中加入路径,我都试了,都不和... 按理说FQ了可以下载了吧,但是半天没下载完,一 ...

  8. 【算法】数组与矩阵问题——找到无序数组中最小的k个数

    /** * 找到无序数组中最小的k个数 时间复杂度O(Nlogk) * 过程: * 1.一直维护一个有k个数的大根堆,这个堆代表目前选出来的k个最小的数 * 在堆里的k个元素中堆顶的元素是最小的k个数 ...

  9. 那些年,我们一起被坑的H5音频

    原文地址:http://weibo.com/p/23041874d6cedd0102vkbr   不要被这么文艺的标题吓到,这里不会跟你讲述中学时期泡妞史,也不会有其它什么现实不该有而小说噼里啪啦不能 ...

  10. 每天一条linux命令——login

    login命令用于给出登录界面,可用于重新登录或者切换用户身份,也可通过它的功能随时更换登入身份.当/etc/nologin文件存在时,系统只root帐号登入系统,其他用户一律不准登入. 语法: lo ...