65.9K
CodeProject 正在变化。 阅读更多。
Home

升级使用 VS.NET 创建的安装程序时,如何卸载以前安装的应用程序 - 第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (55投票s)

2005 年 10 月 2 日

CPOL

25分钟阅读

viewsIcon

302945

downloadIcon

1295

配置您的升级安装程序,以使用 VS.NET 和 Windows 安装程序技术卸载以前安装的应用程序。

背景

首先,感谢大家对我最初提交的文章的关注和宝贵评论。本文现在已经修改了两次,希望是最后一次。我纠正了一些错误和遗漏,并添加了一些补充评论。原始版本“更简单”,但处理安装时事情很少简单。我纠正了其他人指出的一些错误以及我自己发现的错误。文章现在变得更加复杂。这也是我想要呈现的整体方法的第一部分。现在有一个配套的第 2 部分文章。此处显示的技术不会深入探讨 Windows 安装程序设置中称为“组件规则”的规则的详细信息。幸运的是,VS.NET 会在幕后为您强制执行组件规则——只要它能在您构建安装程序的机器上找到您以前的安装程序,或者应用程序本身的以前版本。我们将在第 2 部分中讨论它们。

引言

如何在升级带有 VS.NET 创建的安装程序时卸载以前的应用程序安装是论坛中经常出现的问题。无论您在 VS IDE 中为升级后的应用程序创建新安装程序时选择什么选项,它从不卸载现有应用程序!

大多数开发人员没有意识到的一件事是如何正确使用更新代码,这是触发以前版本卸载的原因。Windows Installer 包含查找和卸载应用程序以前版本的功能。但是,要实现这一点,需要一些在 VS.NET 中未充分记录的步骤。

它首先是配置升级代码——安装程序引擎用于“识别”彼此相关的多个 MSI 包的 GUID。然而,即使您正确配置了更新代码,卸载也只适用于每个用户安装。对于每个机器安装,它总是失败的。

本文介绍了如何正确编写升级安装程序,使其在常见的“实际”条件下进行卸载...

你需要什么

您需要一个名为 Orca 的实用程序,它允许您手动编辑 MSI 包。曾几何时,您可以从 Microsoft 下载 Windows Installer SDK。然而,雷德蒙德的智囊团,以他们神秘的“智慧”,决定使其不可用。现在您必须下载 250 MB 的 Platform SDK 才能获得 Orca 吗?但有个好消息,兄弟姐妹们!您仍然可以获得古老的版本 1 MSI 工具,幸运的是,原始版本的 Orca 将允许您打开 VS.NET 创建的版本 2.0 MSI。(我不知道 2005 年怎么样,但 VS 2002 和 VS 2003 创建版本 2.0 安装程序)。趁现在还能拿到,赶紧来这里吧!(一旦有权力的人看到我发布了这个链接,毫无疑问它就会消失:-) 版本 1 Windows Installer SDK。然后找到并运行 Orca 安装程序——这就是您完成此过程所需的一切!

zip 文件中包含的内容

zip 文件包含两个简单的 Hello World 安装程序。版本 1 安装程序是使用 VS.NET 构建的,在 VS 构建后未对 MSI 进行任何修改——就像您已经投入使用的某个应用程序一样。版本 2 是使用 VS.NET 构建的,然后在构建期间/之后进行修改,以便干净地升级版本 1。

首先运行版本 1,您会看到它安装了一个“帮助”文件和一个指向该文件的快捷方式,用于演示目的。版本 2 不包含“帮助”文件。接下来运行版本 2,您会看到它不仅升级了应用程序版本,而且还卸载了旧的帮助文件和快捷方式,因为它们不再是“新改进”版本 2 的一部分。(我意识到新版本通常包含更多功能,但即使未正确编写以卸载旧应用程序,安装程序也会在现有功能之上添加新功能。因此,该演示旨在证明以前的应用程序卸载可以正常工作。)

MSI 文件的主要目的是,您可以在 ORCA 中查看它们,并查看我所做的更改。

我没有包含任何项目——除非您使用自己的安装程序来完成本文,否则您将无法从中获得太多。请随意使用这两个 Hello World 可执行文件作为源来创建您自己的安装程序,或使用您自己的应用程序。无论哪种方式,您都需要从 VS.NET 开始完成整个过程,以构建您自己的安装程序,然后按照描述修改它们。

配置升级期间的卸载

好的,假设您已经发布了应用程序的第 1 版。现在您准备发布第 2 版,但在此过程中您希望卸载第 1 版。您可以这样做!

1. 查找原始安装程序升级代码

首先,您需要正确配置升级代码。幸运的是,尽管您可能不知道如何使用它,但当您构建应用程序的第一个版本安装程序时,VS.NET 会自动为您创建升级代码。首先您需要做的是找到它。重新打开您为版本 1 创建的 VS.NET 安装程序项目,查看“属性”窗口,您会看到一个名为 UpdateCode 的 GUID。复制此内容——当您为版本 2 创建新安装程序时会用到它。

它看起来是这样的

2. 创建您的版本 2 安装程序

像往常一样创建您的版本 2 安装程序。(我们稍后将讨论一些版本问题,但目前,只需创建您的新 VS.NET 安装程序,但不要构建它。)

3. 配置您的版本 2 安装程序以卸载以前的版本

您需要在版本 2 安装程序中更改两个属性,以触发现有版本的卸载。将 RemovePreviousVersions 属性设置为 True。将 UpgradeCode 属性设置为您从第一个版本安装程序中检索到的 GUID。这是新安装程序查找以前版本以重新安装的方式。保存并构建新安装程序。VS.NET 在识别出 UpgradeCode 已经存在后,将对您的新安装程序应用一些复杂的组件规则,我们暂时不讨论这些规则。(同样,为确保这种情况发生而无需担心细节,只需在已安装以前版本的机器上构建您的新安装程序!)

它看起来是这样的

4. 测试新旧安装程序!

这就是您让它工作所需的一切!嗯,差不多。这就是您需要做的一切……**如果**您的应用程序是按用户安装的,并且**如果您**不担心卸载和重新安装某些文件——例如 Access 数据库。我们稍后会讲到……

我建议您亲自演示一下。如果需要,请先备份,然后手动卸载任何现有应用程序安装。然后运行您的原始“版本 1”安装程序——选择“仅限我”选项。(为简单起见,我们将它们称为“版本 1”和“版本 2”,尽管您的实际版本号可能更高。)现在运行您新创建的“版本 2”安装程序——无论您选择“仅限我”还是“所有人”都无关紧要。请注意,您的版本 1 应用程序已卸载,并且“添加/删除程序”实用程序中只有一个条目。这正是我们希望发生的情况!现在再次清理所有内容。这次运行您的版本 1 安装程序,但选择“所有人”。现在运行您的版本 2 安装程序。您会注意到所有新文件实际上都已升级。但是任何不再需要的旧版本 1 文件也仍在系统上。而且您现在在“添加/删除程序”窗口中同时拥有两个安装程序的条目。这**不是**我们希望发生的情况!

5. 尝试修复还是不修复每台机器卸载失败的问题

简单来说,Windows Installer 中的主要升级只有在版本 2 的每用户或每机器状态与版本 1 的安装方式(每用户或每机器)相同时才能奏效。如果您按照上述建议测试了安装程序,您就会看到这一点。为了解决这个问题,我们应该首先定义一些术语

  • “每用户”等同于使用“仅限我”选项进行安装。
  • “每台机器”等同于使用“所有人”选项进行安装。

由于 VS.NET 创建安装程序包的方式,它总是默认为每用户安装。如果版本 1 是为“所有人”安装的,即每台机器安装,则会使您的卸载失败。即使用户在版本 2 安装中选择“所有人”,该包也会跳过版本 1 的卸载,因为初始默认的每用户不匹配以前的每台机器级别,而且当用户更改此设置时,安装程序已经确定它无法卸载版本 1,因为级别不相同。

请记住,当卸载“不起作用”时,其行为会恢复到您以“传统”方式在 VS.NET 中创建版本 2 安装程序时的行为——您最终会得到一个叠加安装——在“添加/删除程序”中出现两个条目等。

遗憾的是,无法保证您的新版本 2 安装程序始终能在所有可能的部署场景中卸载现有版本 1 安装,即使使用下面描述的修改请参阅第 2 部分,了解解决此问题的自定义操作

现在您必须做出决定——如果您知道应用程序通常是如何安装的,在某些情况下可以“对冲”您的赌注。

选项 1:如果您的部署场景主要适用于拥有管理员的 NT 系统上的业务环境,这可能是最佳选项。

在许多商业环境中,您有一个受管理的系统,其中大多数用户不是管理员,并且管理员通常为每个人安装软件。如果这是您的部署场景,您可能希望修改版本 2 的安装程序行为。您可以将属性 - ALLUSERS - 设置为一个值,该值允许版本 2 对其安装方式做出一些决定。这在大多数情况下都有效——假设管理员通常为他的用户组按机器安装软件。我认为这是一个合理的假设,基本上因为这是管理员管理其用户组最简单的方式,但再次强调,没有任何保证。“典型”管理员就像“典型”用户一样是虚构的,但我无法涵盖所有可能的真实情况。:-)

以下是 ALLUSERS = 2 将导致版本 2 对基本 NT 用户级别 behave 的方式

  1. 受限用户:无法安装程序。由于管理员必须为他们安装程序,最可能的情况是管理员登录到他们的机器,并选择“按机器”,因为如果管理员选择“按用户”,则应用程序只为管理员安装,而不是为用户安装。在这种情况下升级正常工作,因为原始安装是按机器的,并且如果 ALLUSERS = 2,版本 2 安装程序将首先尝试进行按机器安装。
  2. 标准用户:可以自己安装程序,但只能作为每用户安装。不显示“为所有人”安装的选项。升级仍然有效,因为设置 ALLUSERS = 2 会导致安装程序默认为允许的最高级别。在这种情况下,即每用户安装。升级在这种情况下正常工作,因为它被强制默认为每用户级别。
  3. 管理员为机器的所有其他用户安装软件:由于管理员有意为“所有用户”安装软件,他们大概会为版本 1 选择每台机器,并且会再次为版本 2 安装选择每台机器,因为毕竟,他们正在为所有可能访问机器的用户安装软件。升级在此场景中正常工作,因为如情况 1 所示,原始安装是每台机器的,并且如果 ALLUSERS = 2,版本 2 安装程序将首先尝试执行每台机器安装。
  4. 管理员为自己安装软件:在这种情况下,升级可能按预期运行,也可能不按预期运行——如果他们为原始安装选择了按机器的“所有人”选项,并且您将 ALLUSERS = 2,则它会正常工作。

但是,如果他们选择只为自己安装原始版本,即每用户安装,则这两个安装程序将不匹配,这是唯一不会正常工作的情况。

在这种情况下会发生什么,基本上就是您恢复到如果版本 2 不尝试卸载版本 1 时所具有的行为。因此它不会灾难性地失败。它的工作方式与未修改的 VS.NET 版本 2 安装相同。各种文件都已升级,版本 2 在大多数情况下应该安装正确,但您现在在“添加/删除程序”中会有两个条目,等等。

选项 2:如果您的部署场景主要适用于将应用程序安装在自己计算机上供个人使用的用户,则可能是最佳选项。

保持默认行为为每用户。您的版本 1 安装程序以“标准”VS.NET MSI 形式分发。这意味着它默认为每用户,并且由于无法真正知道用户的偏好是什么,因此您最好的假设可能是他们接受了默认设置。

选择哪个选项?我无法为您回答这个问题。我只能告诉您,如果您认为这是您的最佳选择,如何更改 MSI 以使用选项 1。如果您选择选项 2,则无需进行任何修改。

要将安装默认更改为选项 1 中描述的行为,我们需要使用 ORCA 修改 MSI。右键单击您创建的版本 2 MSI,然后选择使用 Orca 编辑选项。这将打开 MSI 数据库表。找到 Property 表并在 Orca 的左侧视图窗口中单击它。选择 Tables,Add Row,然后输入以下名称和值:名称:ALLUSERS 值:2。

您应该会看到类似这样的内容

这将把安装程序的默认启动级别更改为按机器 — “所有人” — 选项。

但是,如果您包含显示此选项的对话框,则默认的单选按钮将不会反映此更改。因此,我们还需要修复它。在同一张表中,您会看到一个名为“FolderForm_AllUsers”的属性。它的值将是“ME”。双击“值”行,您可以编辑该值。将值更改为 ALL

从 Orca 菜单中选择“文件”,然后“保存”以保存 MSI。您还需要关闭它,否则它将无法运行。现在,您的版本 2 安装程序默认为“每台机器”,并将按照上述选项 1 中所述的方式运行。

现在一个迫切的问题是:“有没有办法确保卸载**总是**成功?”答案是肯定的,但这需要一些高级技术。您必须创建一个自定义操作来查找版本 1 的现有安装,查看它是如何安装的,并设置您的版本 2 安装以匹配该行为。请参阅第 2 部分以了解如何做到这一点

同时,尽管我没有给您一个“完美”的解决方案,但最坏的结果与您使用不尝试卸载版本 1 的标准 VS.NET 版本 2 没有任何不同。

6. “天啊,我的现有数据去哪儿了???”或如何防止不必要的文件替换

上述技术工作良好——只要你不介意版本 1 的所有内容都被卸载。假设你需要一些旧文件?只需将它们放入版本 2 的安装程序中——无论如何你都需要它们,因为一个人可能会进行版本 2 的新安装,对吗?

嗯。也许吧。但是假设您的应用程序使用 Access 数据库。并且这个人一直在使用版本 1。这意味着他们有数据。尽管您也将您的 Access 数据库放入版本 2 中,但突然您的电话开始响个不停,愤怒的客户问道:“我升级到版本 2。我的现有数据去哪儿了?”

问题是版本 2 MSI 首先完全卸载了版本 1,然后完全安装了版本 2 中的所有文件。因此,现有数据库文件——包含您客户的数据!——首先被删除,然后被版本 2 安装程序中“出厂时无用户数据”的副本替换。您能阻止这种情况发生吗?是的!

第一种方法是将您的 Access 数据库标记为永久。这是您在创建原始安装时应该做的。您不应该卸载包含用户数据之类的数据库。如果他们希望在卸载应用程序时删除数据库,他们可以选择这样做。

如果您将数据库标记为永久,则无需担心安装顺序或覆盖现有数据库。如果您在版本 1 安装程序中接受了 VS.NET IDE 默认设置,则数据库默认不会标记为永久。您应该在第二个版本中将数据库标记为永久。您可以从该错误中恢复,并且仍然可以升级而不会丢失旧数据。但同样,与 MSI 修改中的大多数事情一样,存在一些潜在的陷阱。我要感谢 Philip 纠正了我原始版本中关于此主题的一些不准确之处。有关更多信息,请参阅下面的帖子。

我将介绍两种控制卸载/升级/重新安装行为的选项,这些选项由 Windows Installer 的执行顺序决定。(实际上,您有两个以上的选项,但它们需要超出本文范围的修改。)

选项 1:第一个选项可以看作是“先卸载原始版本,最后安装新版本”。

如果您选择“先卸载旧版本,后安装新版本”,则 MSI 的行为简单且可预测。这就像您手动卸载了原始应用程序,然后安装了新版本一样。版本 1 中安装且未标记为永久的任何文件都将被删除。版本 2 安装的所有文件都是实际包含在版本 2 安装程序构建中的文件。

注意:如果您不希望保留任何现有文件,则应使用此选项。它简单明了,可确保最终在目标机器上存在的所有文件都是您在版本 2 安装程序中包含的文件。

此选项会完全清除原始安装,并完全安装版本 2 安装程序,就像是“全新、原始”安装一样。此方法保证目标机器上存在的所有文件都是版本 2 安装程序中包含的最新文件。

如果您**绝对**确定在升级过程中无需保留版本 1 使用的任何内容,则应选择此选项,因为它保证了版本 1 安装的现有文件的干净卸载,以及版本 2 中所有文件的干净全新安装。如果您希望以这种方式执行升级,那么您已完成此操作 - 无需再修改版本 2 MSI。

选项 2:第二个选项可以看作是“先安装新版本,最后卸载旧版本”。

假设您的应用程序安装了一个 Access 数据库?如果您选择“先卸载旧版本,最后安装新版本”选项,则现有 Access 数据库将被删除,然后安装新的“出厂版本”。这当然意味着您的用户将丢失在使用版本 1 时可能已添加到 Access 数据库中的所有历史数据。但是,如果您修改 MSI,使其使用“先安装新版本,最后卸载旧版本”选项,则可以防止这种情况发生。

此方法可用于保留现有文件,例如已在使用中的 Access 数据库。但它也带来了更复杂的可能行为,如果您使用此方法,则必须熟悉 MSI 如何替换或不替换版本 2 安装程序中包含的现有文件。再次感谢 Philip 纠正了我第一次提交中的一些错误。

我将尝试以最简单的方式呈现一些基本规则。因此,以下是最重要的可能性摘要,从最简单到最复杂

  1. 在版本 1 安装期间安装在目标计算机上,但不再是版本 2 一部分的**任何**文件都将**始终**被删除——假设它们未被标记为永久。
  2. 如果文件有版本,例如应用程序或 DLL,则最高版本总是胜出。这总是你想要的,除非你是一个心理变态的开发人员。
  3. 当没有版本的文件具有相同名称且同时存在于两个版本中时,事情会变得棘手。

如果文件,例如 *MyFile1.ext*,已经安装在目标机器上,并且在版本 1 安装后**没有**被修改,并且版本 2 安装程序中包含完全相同版本的文件,那么这些文件是完全相同的,并且没有“未版本化”问题。*MyFile1.ext* 将在版本 2 安装程序完成后存在于目标机器上。

如果一个文件(例如 *MyFile2.ext*)已经安装在目标机器上,并且在版本 1 安装后**没有**被修改,并且版本 2 安装程序中有一个较新的文件副本,即版本 2 安装程序中的 *MyFile2.ext* 的“上次修改日期”**晚于**目标机器上已存在的 *MyFile2.ext*,那么“上次修改日期”最高的文件胜出——版本 2 安装程序中包含的 *MyFile2.ext* 副本将在版本 2 安装程序完成后存在于目标机器上。

但是,如果文件(例如 *MyFile3.ext*)已安装在目标计算机上,并且在版本 1 安装程序安装后也**被修改过**,那么**现有文件始终优先**,并且版本 2 安装程序中包含的 *MyFile3.ext* 文件副本在版本 2 安装程序完成后将**不会**安装在目标计算机上。

这通常会强制执行您想要的行为。例如,如果您有一个现有的 Access 数据库,那么目标机器上已有的数据库的“上次修改日期”将与它首次由版本 1 安装的日期不同——因为应用程序在数据库首次安装后某个时间点使用了它——并且它不会被版本 2 安装程序中包含的 Access 数据库替换。

然而,这也意味着,例如,如果您有一些由版本 1 安装,然后又被修改过的 .xml 配置(CONFIG)文件,它们将**不会**被版本 2 中同名文件替换。这可能是一个潜在的问题。

选择哪个选项?

同样,我无法为您回答这个问题。我只能告诉您,如果您认为这是您的最佳选择,如何更改 MSI 以使用选项 2。如果您知道您正在面临“混合场景”——您有一些文件必须保留,但也有一些文件必须替换,并且您有充分的理由相信选项 2 不会奏效,那么您确实需要一些更好的工具来处理。假设您没有这些工具,您将需要诉诸自定义操作。

同样,您有两个基本选项。第一个是运行一个自定义操作,将现有文件备份到临时文件夹,使用“选项 1”完全安装,然后运行一个最终自定义操作,将旧文件从临时文件夹移回。第二个是运行“选项 2”安装以保留现有文件。此安装程序还需要将未版本化文件的新副本安装到辅助文件夹。然后您需要运行一个最终自定义操作,覆盖这些文件以将其更新到所需的文件夹中。

每种方法都有其优缺点。通常,我更喜欢最后卸载的方法,因为我认为它适用于许多常见场景。如果它在我的所有测试中都有效,但偶尔在现场未能安装更新的文件,我宁愿处理那个问题,而不是删除我的客户数据。

但是,如果您有理由确信“最后卸载”不起作用,那么保存现有文件是“更干净”的更好方法,但请注意,您**不能**使用 VS Installer 类自定义操作来执行此操作,因为此类自定义操作直到安装结束才运行——那时保存现有文件为时已晚。

如果您选择第二个选项,您可以使用安装程序类。但是有很多负面后果。首先,您必须将文件的副本留在辅助文件夹中,否则 MSI 会检测到它们“丢失”并再次运行以“修复”“问题”。此外,版本 2 MSI 不知道如何卸载文件的最终副本——它们不在它放置它们的位置。因此,如果用户完全卸载应用程序,它们将被留下。

复杂性,复杂性,复杂性!

但是,我不能在一篇文章中告诉你如何解决世界上所有的问题,所以... :-) 回到手头的工作:如果你选择选项 1,同样不需要进一步的修改。那么如果我们想使用选项 2——先安装新应用程序,然后删除现有应用程序呢?我们可以通过先安装版本 2,最后删除版本 1 来改变执行顺序,以强制执行这种行为。

回到 ORCA。重新打开版本 2 MSI,找到名为“InstallExecuteSequence”的表。首先单击右侧视图中的序列标题以找到最高序列号。记下这个数字——在我的安装中是“6600”。现在单击动作标题以按字母顺序排序。找到名为:RemoveExistingProducts 的动作。将其值更改为高于任何现有值的值——我使用了 6700。

保存,Orca 的工作就完成了。

通过此处所示的先安装后卸载设计,MSI 操作消除了对复杂条件安装操作的需求,因为执行序列将强制执行应用程序在多个版本升级生命周期内的最终镜像——*只要您仔细进行文件组织、版本控制和日期控制!* 缺点是,在某些情况下可能不会安装较新版本的文件,如上述规则所述。

7. 测试,测试,测试

显然,设置非常复杂。对于升级现有应用程序的复杂性,绝对没有完美的解决方案。我无法为您提供在您的具体情况下如何操作的准确答案。我唯一能告诉您的是,反复测试,直到您确信自己了解所选择的安装配置的行为。

我们完成了!

其他问题

假设你说,“但是我的版本 2 使用了我的 Access 数据库的新设计!”抱歉!这不是 Windows Installer 问题。在保留现有数据的情况下,将现有数据库架构升级到新架构是一个复杂的问题,取决于许多因素。这不是本文的重点。:-)

另一个问题是自定义操作:MSI 不会记录自定义操作中发生的任何事情,因此后续版本的 MSI 不会卸载这些自定义操作,除非您在原始安装程序中明确编写了在卸载时运行的自定义操作。通常,这会强制执行您想要的行为——例如,如果您的版本 1 有一个自定义操作,从脚本构建 SQL Server 数据库,并且您可能没有编写卸载操作。您的版本 2 安装将不会以任何方式影响您的原始数据库构建。

与升级现有数据库一样,版本升级之间自定义操作的更复杂问题超出了本文的范围。

MSDE 和合并模块

最后我想至少评论一下一个领域。我没有考虑一些问题,例如,您的安装程序是否也安装 MSDE,或者使用其他合并模块。在没有 InstallShield 等专业工具的帮助下,使用 Windows Installer 编写复杂的安装程序确实相当困难。

对于 MSDE,您可能知道,有许多细节使得这种安装尤其棘手。此外,Microsoft 在 MSDE 的历史中多次更改了他们的方法。首先它就像 SQL Server 一样——您必须将其与任何应用程序安装分开安装。然后发布了各种 MSDE 的合并模块,但大多数(如果不是全部)都有各种错误和其他问题。上次我检查时,Microsoft 建议不要将合并模块用于 MSDE,因此您大概又回到了单独安装。

这一直是一个令人困扰的问题,尽管我感到它需要解决。我认为这不是一篇关于该范围的文章——也许将来我会尝试解决这个问题。

结论

本文根据 Philip 指出的一些不准确之处以及我发现的一些错误进行了实质性修改。我建议您阅读下面给出的技术评论。处理安装程序非常复杂,您永远不会有太多的接触或视角。最近,我提交了一篇配套文章第 2 部分,解决了每用户/每机器问题。在发布之前,务必在尽可能多的条件下仔细测试任何安装程序。

© . All rights reserved.