最新消息:20181230 VPS服务器已从Linode换到腾讯云香港,主题仍用朋友推荐的大前端D8

【已解决】给一个C#的Dll库的项目,制作msi安装包 + 【已彻底搞懂原因】制作出来的msi的installer,在卸载程序的时候,在删除本程序所创建的注册表项的同时会把父级注册表项删除掉

C# crifan 2208浏览 0评论

【背景】

已完成一个Windows Live Writer(WLW)的一个插件,是C#实现的dll库的形式的一个项目。

现在需要制作对应的msi的安装包。

其实呢,如果只是让此WLW的插件可以用,直接通过拷贝此C#所生成的dll文件,放到WLW的Plugin目录下,也就可以了。但是希望是不需要用户操心,所以还是制作出对应的msi安装包,双击安装,一切搞定,比较易用。

因此,希望制作出来的msi安装包,需要先检测出来WLW已经安装,然后拷贝对应的dll到WLW的plugin目录,然后根据官网的说明:

http://msdn.microsoft.com/en-us/library/aa738841.aspx

添加对应的注册表项。

这样,才算一个比较完全和智能的安装包。

【解决过程】

1.网上搜了一堆资料,除了个别的帖子:C#如何为winform程序打包发布应用(图解教程) 中提到,如何创建C#的安装项目:

新建安装项目

也没找到注册表的编辑界面,是从哪里打开的。

后来,才发现,原来是通过,右击项目->视图->注册表,而打开的注册表编辑器,可以增删对应的注册表项。

右击项目 视图 注册表

这下,就可以慢慢去折腾,到底是如何添加对应的注册表项的了。

注:后来在这里启动条件编辑器看到的,原来通过 视图->编辑器:

视图 编辑器

也可以找到这几个相关的编辑器的,包括文件系统,条件编辑器等。

3.同时,也看到上面的视图中,是有个“启动条件”的,然后又去打开了启动条件,新建了个启动条件的项:

启动条件然后设置Property为WLWINSTALLDIR,接着,再去设置一个启动条件condition:

condition example

message设为“Before install this plugin, please install Windows Live Writer first.”,表示如果没有满足WLWINSTALLDIR的condition,就会给出对应这段提示。

然后再去对应的在文件系统或注册表中,操作(添加/删除/修改)对应的文件或注册表项。然后每个操作项,都有一个condition,此处设置为我上面所设置的hasInstallWlw,然后才会操作这些文件或注册表项:

满足condition时候的注册表操作

搞懂了这套基本的逻辑,接下来,就是一点点按照自己的思路,去实现对应的安装部署的过程了。

5.进过一番折腾,对于最开始想要通过注册表判断,是否能在注册表的

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Live\Writer\

中找到InstallDir,然后得到其值,就可以再去设置将dll拷贝到此目录下的plugins目录中去了。

所以去做了如下设置:

1.创建了名为searchWlwInstallDir的注册表搜索项:

创建 注册表搜索项

创建了searchWlwInstallDir的注册表搜索项

其RegKey设置为SOFTWARE\Microsoft\Windows Live\Writer\InstallDir,即希望搜索这个值,如果能找到,那就说明当前已经安装了WLW了。

然后将找到的此值对应的Value,保存为installedWlwDir,表示找到的已经安装了的Wlw的安装路径。

对应的Property命名为WLWINSTALLDIR。

2.然后也建立了一个名为hasInstallWlw的启动条件:

添加了对应的启动条件 hasInstallWlw

但是,设置好后,其condition设置为上面的WLWINSTALLDIR,然后Message设置为Before install this plugin, please install Windows Live Writer first.,这样如果安装过程中,不满足此条件,则会对应的这个错误提示内容。

3.后面又添加了一些其他的配置,去创建文件夹,拷贝dll等。

4.然后生成解决方案后,想要去找到此 安装和部署项目的调试方法,没有找到。

然后发现只能通过右击项目,选择 安装,来实现立即的测试而已。

结果选择安装,却出现了上面所设置的错误提示“Before install this plugin, please install Windows Live Writer first.”,然后安装就失败了。

5.然后就去搜索解决办法。

后来google“C#  search target computer”,找到:

http://msdn.microsoft.com/en-us/library/k3bb4tfd(v=vs.80).aspx

http://www.codeguru.com/csharp/.net/net_vs_addins/visualstudioadd-ins/article.php/c7245

然后再搜“Registry Launch”找到微软的官方例子:

http://msdn.microsoft.com/en-us/library/h0z89y5d.aspx

然后才搞懂,原来是上面的设置中,RegKey,应该是SOFTWARE\Microsoft\Windows Live\Writer,然后Value是InstallDir,这样才是去搜索

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Live\Writer\InstallDir

然后再去尝试安装,就没有出现上述错误提示了,即找到了上面这个注册表项。

6.然后接着去安装,启动后,就已经可以找到我所设置的对应的安装路径了。

不过在此之前,先说我是如何做的:

(1)先通过右击“应用程序文件夹”->添加->程序集:

右键 添加 程序集

然后浏览找到我的项目所生成的dll:crifan.InsertSkydriveFiles.dll,添加进来后,系统会自动检测到一堆的依赖项:

一堆的依赖项

而都把他们排除掉了。

然后对于的确所需要的微软的.NET Framework,双击后,会自动在启动条件中出现.NET Framework,然后对于其属性,把Version从默认的4.0的改为我的原先C#的dll项目所需要的2.0:

设置Microsoft .NET Framework 为2.0

(2)然后对于“应用程序文件夹”的属性,把DefaultLocation设置为我所需要的[ProgramFilesFolder]\Windows Live\Writer\Plugins,把condition设置为前面的WLWINSTALLDIR:

设置defaultlocation和condition

如此都设置好后。然后用生成的安装程序去安装:点击 安装,启动后,就可以看到对应的安装向导了:

 

安装向导

确认安装:

确认安装

如此,一个最基本的安装工具,就完成了。

7.不过还是想要实现别人的那种,不需要用户选择安装路径,而直接安装的那种。

因为此处是定死的,必须装到WLW的Plugins目录下的,然后才WLW才可以识别的,所以,此处不需要麻烦用户再去指定安装路径,而应该是直接安装包固定对应的路径,然后直接拷贝对应dll文件即可。

然后此处遇到个问题,那就是,想要把dll拷贝到的目标路径,其实取决于之前搜索注册表项:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Live\Writer\InstallDir

所获得的值,来决定的,而如何引用到上面的值,一直没搞懂,后来在这里:

http://msdn.microsoft.com/en-us/library/h0z89y5d.aspx

看到了具体的方法。那就是,对于上面的注册表搜索,对应的值,是通过Property的名字,加上对应的中括号来获得的。

即上面的property是WLWINSTALLDIR:

搜索注册表的property所以对应的要把dll拷贝到的路径就是[WLWINSTALLDIR]\Plugins:

然后拷贝的路径,就是根据前面的搜索结果来决定的

竟然还有原先的这个草稿的,即,虽然WLW注册表项被破坏了,被删除了,但是本地草稿的内容,还是存在的。

所以,觉得WLW还是做得很不错的。所以特此感谢一下。

 

继续上面的制作installer的过程。

目前的情况是,虽然可以制作出来对应的msi的安装包了,而且也可以写入注册表项了。

但是在卸载的时候,会删除父级的注册表项:PluginAssemblies在卸载时候 会删除其parent的注册表项

其中,对于新添加的PluginAssemblies注册表项的属性部分:

PluginAssemblies的属性

此处截图的设置是:

AlwaysCreate = True

DeleteAtUninstall = False

Transitive = True

结果是卸载的时候,还是会删除对应的其父级的注册表项,即:

HKCU\Software\Microsoft\Windows Live\Writer,中的所有的项:

writer下面的注册表项

即上图中的glinks,preference,update等等,全部都会被删除,然后才导致重新打开wlw,会丢失之前的所有的设置的。

然后关于AlwaysCreate 和Transitive 的设置,也都试过false和true的各种搭配,结果都还是不行,还是会在卸载的时候删除writer下面的所有的注册表项,而不是期望的,只删除手动创建的

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Live\Writer\

下面的crifan.InsertSkydriveFiles。

所以很是悲剧。暂时还没解决这个问题。

如果设置,才能在卸载的时候,只删除本程序所创建的,最后一级的注册表项crifan.InsertSkydriveFiles,而不是把其上一级Writer下面的所有的注册表项都删除。

 

【后记 2012-03-04】

后来经过尝试,找到了解决办法,即如何保证卸载的时候,只卸载对应的自己的程序所新建的注册表项,而不去删除该注册表项的父级(上一级,上两级等)的注册表项。

即,对于每一级的注册表:

从最开始的各个父级的注册表项:

software 一级

microsoft一级windows live一级writer一级pluginassemblies一级

对应的设置是:

condition,都为空,都不用填写啥前提条件,因为本程序都没去创建对应的注册表项,人家WLW原先就已经创建了,所以不需要设置对应的condition。

AlwaysCreate设置为false,即不需要自己创建,因为本来这些项,就是存在的。

Transitive设置为false。这个参数需要特别注意,虽然官方对于此项的解释:

http://msdn.microsoft.com/en-us/library/928tb19t(v=vs.71).aspx

说是不要轻易设置为false。但是我此处经过实际测试,之前会误删注册表,就是由于这个Transitive设置为True了。

 

到本程序所新建的注册表项crifan.InsertSkydriveFiles一级:

自己程序所新建的注册表项 一级

所有的transitive,也要设置为false,否则就会删除上一级的注册表项!!!

对于前提条件condition,此处最好填上对应的条件,毕竟是自己程序所创建的注册表项,所以需要自己根据条件来决定是否添加。

 

【题外话】

在找关于在删除本程序所建注册表项同时会删除父级注册表项,别人也遇到类似问题了:

My setup delete a parent registry key which is not created by it

Registry Condition property deleting the complete registry setting on uninstall, Possible Bug!!!!

尤其是第二个人,遇到的问题和我是一样的。

即,对于最开始为本安装程序创建注册表项的时候,是自己手动从HKEY_CURRENT_USER下面,依次,右键,“新建键”,而建了:

Microsoft\Windows Live\Writer\PluginAssemblies

最后在PluginAssemblies之下,添上自己要的注册表项crifan.InsertSkydriveFiles的。

而对于卸载程序会删除掉PluginAssemblies,甚至Writer,即把父级的一些注册表项,都干掉了,所以直观的理解是,估计是卸载程序以为这里的Microsoft,Windows Live,Writer,PluginAssemblies,都是手动自己创建的,所以在卸载的时候才会给一起卸载掉,所以也曾想过,如果这些项,可以不用手动创建,而是对于最后一个crifan.InsertSkydriveFiles注册表项中,直接给出注册表的绝对路径,即HKCU\Microsoft\Windows Live\Writer\PluginAssemblies,那么卸载程序,估计就不会去删除父级的注册表项了。

但是,实际上,却发现,新建的crifan.InsertSkydriveFiles,或者是其父级的PluginAssemblies的属性中的FullPath,都是灰色的HKCU\Software\Microsoft\Windows Live\Writer\PluginAssemblies,是不可改变的,所以,也就没法实现上面的想法,手动输入绝对路径了。

而如果对于要添加的项crifan.InsertSkydriveFiles,写出带绝对路径的

HKCU\Software\Microsoft\Windows Live\Writer\PluginAssemblies\crifan.InsertSkydriveFiles,那么你也是无法通过编译的,因为名字非法。

所以,看似是一个无解的过程。

最后实际上,是经过折腾后,才发现,原来是上面的transitive惹的祸,虽然不是完全的清楚这个选项的用途,但是确定就是这个选项,如果设置为true,会导致卸载会删除父级注册表项,所以,此处解决办法很简单,就是把你所要建的注册表,及其父级的注册表的transitive都改为false,即可。

所以,还是那句话,微软在有些细节方面,貌似也还是设计的有些诡异,至少让很多人,不止我一个,无法完全搞懂其参数的含义,导致出现这类诡异的事情。

 

【总结】

1.对于制作msi/installer来说,添加文件系统中对应的文件部分,相对不复杂,大不了是多摸索,也就能弄出来。

相对比较让人头大的是,对应的注册表的操作,主要是在程序安装的时候,能安装到对应的位置,而且保证程序卸载的时候,会卸载对应的本程序所创建的注册表项尤其需要注意的是,一定要把Transitive设置为false,否则就会在卸载程序的时候,在删除本程序自己的注册表项的同时,也把其他原先就已经存在的,父级的注册表项也同时删除掉了。

 

【后记】

刚才以为自己解决了,实际上还是没彻底解决,关于卸载时删除父级注册表的问题。

最后的最后,还是会删除crifan.InsertSkydriveFiles的父级PluginAssemblies,只是暂时不会删除更上一级的Writer而已。

 

【后记2】

垃圾的installer啊,经过刚才更多次的测试,结果是,有时候会删除PluginAssemblies,而有时候却又不会删除PluginAssemblies,所以很是无语。。。

不知道具体删除或不删除的规则是啥。。。

 

【后记3】

后来把生成的msi,拿到另外一台XP的机子上测试了一下,安装此WLW插件是可以的,可以拷贝dll文件,也可以创建对应注册表项,但是卸载的时候,还是会把PluginAssemblies项删掉,所以还是有问题。感觉的确是.NET的一个bug。

【后记4  2012-03-07】

经过一番测试,证实了这个帖子:

My setup delete a parent registry key which is not created by it

中所看到的现象,即,如果MSI的uninstall去卸载你之前安装的文件的话,

去删除对应程序所创建的某个sub key 的话,如果删除sub key之后,对应的parent key是空的,则会同时也把parent key也删除掉:

而我为了验证那个帖子的结论,专门去测试了一下,在

HKEY_CURRENT_USER\Software\Microsoft\Windows Live\Writer\PluginAssemblies

下面单独随便建立了一个名为makesureNotEmpty的sub key,值为“just make sure this is not empty, then check whether uninstaller will delete the parent registry or not”,然后,在我的msi的installer安装后,会在PluginAssemblies下面生成对应的subkey:

crifan.InsertSkydriveFiles = C:\Program Files\Windows Live\Writer\Plugins\crifan.InsertSkydriveFiles.dll

然后卸载程序uninstall的话,就只会删除程序自己所创建的crifan.InsertSkydriveFiles ,而不会同时删除去parent key,即PluginAssemblies了,因为PluginAssemblies不为空。

至此,此问题,算是有了个最终的结论。

【结论】

如果你的安装程序在安装过程中,创建了某个注册表项subkey,在卸载的时候,会删除自己程序所创建的subkey的同时,如果发现其parent key是空的话,没有其他subkey了,那么也会同时删除此parent key的。

此问题,的确是属于诡异的行为。正如上面那个帖子中最后那人说的“very old Windows Installer behavior”。

严格意义上说,此问题,是一个bug,不知道微软啥时候能解决掉。。。

注:对于此处的所用到的环境是:installer 3.1, + .NET 2.0

转载请注明:在路上 » 【已解决】给一个C#的Dll库的项目,制作msi安装包 + 【已彻底搞懂原因】制作出来的msi的installer,在卸载程序的时候,在删除本程序所创建的注册表项的同时会把父级注册表项删除掉

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
55 queries in 0.132 seconds, using 10.09MB memory