前面我們已經知道怎麼製作一個完整安裝包了,但我們的軟件往往不能一次性就滿足客戶的需要,當客戶需要我們給軟件進行升級的時候,我們應該怎麼做呢?
在這之前,我們有必要了解下Windows Installer中的Upgrades定義:
6.1 關於Windows Installer Upgrades
在Windows Installer中將軟件產品的更新劃分爲3類:
- Small updates 它意味着安裝包裏一個或幾個文件的很小的改變,使用Small updates時不需要改變Version屬性,也不需要改變Product GUID和UpgradeCode屬性。唯一要改變的只有Package GUID,事實上我們每次重新生成安裝包都需要改變Package GUI。如果是同一安裝包,即Package GUID不改變,你在安裝程序後,再點擊安裝包,會彈出維護的界面(卸載和修復);如果是Package GUID改變了,則會彈出錯誤:“已安裝了該產品的另一個版本。無法繼續安裝此版本…”。
注意任何時候重新生成安裝包一定要更改Package GUID,使用不同的Package GUID可以方便管理安裝的更新包(msp),不同的Package使用相同的GUID會造成安裝程序混亂,造成意想不到的後果。那麼既然Small updates時更改Package GUID後運行會彈出錯誤,那麼怎麼應用Small updates呢?我們可以用命令行方式執行安裝程序,或者通過執行Patch更新包(msp)。製作Patch更新包後面會詳細講,我們先看看命令行方式:
msiexec /fvomus [path to updated .msi file]
或者
msiexec /I [path to updated msi file] REINSTALL=ALL REINSTALLMODE=vomus.
- Minor upgrades 在這種情況下,需要更改product version,當然Package GUID也要改變,Product GUID和UpgradeCode屬性仍然保持不變。這種方式允許我們添加新的features和components,但是不能改變feature-component樹的組織結構。Minor upgrades和Small updates很相似,似乎只是多了個版本變化而已;事實上,可以用Small updates的地方肯定也可以用Minor upgrades,應用Minor upgrades的方式跟應用 Small updates一樣,可以使用命令行或者patch方式。
- Major upgrades 在這種情況下,需要更改Version 屬性, Product 和 Package GUID,我們仍然保持UpgradeCode屬性不變,大家可能會注意到,UpgradeCode在這裏似乎沒什麼用處,先別急,下面我們會馬上講到它的用處。先看看Major upgrades ,事實上,對開發人員來說,它是一個最安全和最便捷的升級方式,因爲Product GUID已經改變,意味着它可以和老版本共存安裝到一臺計算機上,並且是一個完全的全新安裝。
關於Product/@Id屬性(GUID),Product/@Version屬性,Product/@UpgradeCode屬性(GUID),還有Package/@Id屬性(GUID);這幾個屬性在第一章有提到過,在這裏我們就可以更清楚他們的意義了
6.2 檢測並替換現有版本(Major upgrades)
講到這裏,大家應該對Windows Installer製作升級的基礎知識有了一定的瞭解。但是如果我們的安裝包體積不大,希望使用Major upgrades,但是又不想每次安裝新版本時要手動卸載以前的版本,要怎麼做呢?
最好是方式是讓安裝程序能檢測以前版本,然後刪除以前版本,最後安裝新版本程序;要想能檢測到以前版本的信息,這時UpgradeCode就起到作用了。當我們製作更新包或升級版本時,首要要確保我們有以前老的版本的安裝包;另外就算我們不打算當前版本被升級,也必須包含UpgradeCode屬性,因爲一旦你沒提供UpgradeCode屬性,以後就沒有辦法再提供了;我們還應該知道什麼時候應該改變UpgradeCode屬性,事實上,我們在開發一個項目時,可以一直保持UpgradeCode不變,即使Product GUID已經改變,當然你也可以改變UpgradeCode以更好的管理程序版本。
要解決我們的問題還需要引入UpgradeVersion標籤,它能幫助我們檢測已安裝的版本信息和將要升級的版本信息。
<Upgrade Id="F4F8195E-E907-42dd-BB90-CC2403FA7384">
<UpgradeVersion OnlyDetect="no" Property="PREVIOUSFOUND"
Minimum="1.0.0" IncludeMinimum="yes"
Maximum="$(var.Version)" IncludeMaximum="no" />
<UpgradeVersion OnlyDetect="yes" Property="NEWERFOUND"
Minimum="$(var.Version)" IncludeMinimum="no" />
</Upgrade>
Upgrade的Id屬性是我們安裝的以前版本的UpgradeCode,如果你的程序應用了多個UpgradeCode,那麼你只需要添加一個新的Upgrade標記就可以了,每個Upgrade標記包含自己的版本範圍。我們可以看出來,要想更好的管理版本,我們不應該頻繁更換UpgradeCode,不然這裏就要寫很多個Upgrade標記了,對應還要寫很多custom action;我們最多在每個大版本的時候更換UpgradeCode就足夠了,比如1.x版本時使用一個UpgradeCode,2.x時使用另外一個UpgradeCode;當然你也可以選擇不更換UpgradeCode。
Minimum 和 Maximum指定Upgrade中我們應該升級的版本範圍,IncludeMaximum 和 IncludeMinimum 指定升級範圍是否包含邊界值(IncludeMinimum='no' 表示只查找以上版本)。
使用Upgrade 標記後將會觸發一個新的標準動作FindRelatedProducts,它在LaunchConditions動作後執行,當然我們可以在InstallExecuteSequence中重定義它的執行順序,但一般情況下我們不用這麼做。FindRelatedProducts動作 通過Upgrade 標記檢查其中的所有版本,如果找到了,則它的Product GUID將會被追加到UpgradeVersion標記中指定的Property中(如示例中的PREVIOUSFOUND和NEWERFOUND);這裏的UpgradeVersion的Property屬性,相當於定義了2個Property:PREVIOUSFOUND和NEWERFOUND。
OnlyDetect='yes' 告訴我們安裝程序不會移除以前的程序嗎,如果我們是Major upgrades ,將OnlyDetect置爲no,則會刪除以前版本的程序,從而可以保證目標計算機中只有一個版本的product;如果是Minor upgrades ,我們需要將OnlyDetect置爲yes。
如果開發一個本地化的軟件包,你也可以在UpgradeVersion 中指定Language屬性。
注意在這裏我定義了2個UpgradeVersion,他們的Property屬性分別是PREVIOUSFOUND和NEWERFOUND。PREVIOUSFOUND是查找以前安裝的版本,最低版本是1.0.0,它是始終不變的;最大版本是當前版本,需要在編譯的傳進來,這樣做的目的是每次改變版本不用去修改源代碼中的版本號;OnlyDetect置爲no表示會刪除掉找到的安裝程序版本。比如當前版本是3.0.2,則在安裝當前版本過程中,在版本1.0.0和3.0.2之間的任何版本都會被刪除,包括修訂版本。移除以前版本完全是自動的,如果我們要在移除以前版本前做任何事情,我們可以寫一個custom action,設置它的condition爲UPGRADINGPRODUCTCODE 屬性的值。Windows Installer只是在自動刪除的進程裏纔會設置這個屬性,在添加/刪除程序裏手動刪除程序不會設置該屬性的值。
那麼爲什麼要設置NEWERFOUND呢,試想如果你已經安裝了2.0版本的程序,然後又安裝1.0的版本的程序,一般情況下安裝會正常進行,安裝程序不能自動刪除新版本的程序,這時候我們檢測到新版本程序後就需要彈出錯誤提示,提示用戶已安裝更新的版本。如何做到這點呢,我們需要添加一個custom action,以下代碼在FindRelatedProducts後如果找到新版本,則會執行NoDowngrade 的custom action,該action會彈出一個錯誤提示,阻止安裝程序繼續運行。
<CustomAction Id='NoDowngrade' Error='已經安裝了較新版本的 [ProductName],無法繼續安裝該版本應用程序 .' /> <InstallExecuteSequence> <Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom> </InstallExecuteSequence>
到這裏大家應該知道爲什麼我說Major upgrades 是最安全的,因爲不管你之前安裝的是什麼版本,都會刪除掉然後安裝當前版本。而對於Minor upgrades,你需要針對每個不同版本安裝包製作Patch更新包(msp),比如從1.1.0到1.1.1,從1.1.1到1.1.2,如果想從1.1.0到1.1.2,則需要重新制作Patch更新包(msp),當然也不是不可以製作通用於所有版本的Patch更新包(msp),前提是你要有所有版本安裝包(msi),製作方法相對也比較繁瑣,而且Minor upgrades還有諸多限制,如果處理不好會導致安裝錯誤。當然如果你的安裝文件體積太大,使用Major upgrades 就不合適了,一般情況下我們使用Minor upgrades ,下面我們就看看如何爲Minor upgrades 製作Patch更新包(msp)。
製作製作Patch更新包之前,需要注意以下情形:
- 當你需要新老版本共存時,則必須使用Major upgrades。
- 當你需要更改生成的msi文件名稱的時候,不能使用Minor upgrades或Small updates
- 當你更改了Packge裏任何Component的GUID時,不能使用Minor upgrades或Small updates
- 當有組件被移除時,不能使用Minor upgrades或Small updates
- 當更改了Feature的組織結構,比如在Feature中添加或刪除子Feature時,不能使用Minor upgrades或Small updates
如果新版本滿足這些條件,我們就可以開始製作更新包了, 這裏我們將在新版本(1.0.1)安裝中添加一個dll文件,然後更改exe執行文件。製作步驟如下:
1、編譯生成1.0.0版本安裝文件
candle.exe -dVersion=1.0.0 -ext WixUtilExtension -ext WixSqlExtension Sample.wxs DbConfigDlg.wxs -out 1.0.0/ light.exe -loc WixUI_zh-cn.wxl -ext WixUIExtension -ext WixUtilExtension -ext WixSqlExtension -out 1.0.0/Sample.msi 1.0.0/Sample.wixobj 1.0.0/DbConfigDlg.wixobj
2、 接着把1.0.0文件夾的內容複製到新的文件夾1.0.1中,用記事本隨意修改FoobarAppl10.exe文件和Manual.pdf文件,以區別於1.0.0版本;
3、編譯生成1.0.1版本安裝文件
candle.exe -dVersion=1.0.1 -ext WixUtilExtension -ext WixSqlExtension Sample.wxs DbConfigDlg.wxs -out 1.0.1/ light.exe -loc WixUI_zh-cn.wxl -ext WixUIExtension -ext WixUtilExtension -ext WixSqlExtension -out 1.0.1/Sample.msi 1.0.1/Sample.wixobj 1.0.1/DbConfigDlg.wixobj4、創建wix源文件Patch.wxs,內容如下:
<?xml version='1.0' encoding='utf-8'?> <Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'> <Patch AllowRemoval='yes' Manufacturer='Acme Ltd.' MoreInfoURL='www.acmefoobar.com' DisplayName='Police1.0.1.0 Patch' Description='Minor Update Patch' Classification='Update'> <Media Id='5000' Cabinet='SamplePatch.cab'> <PatchBaseline Id='SamplePatch' /> </Media> <PatchFamily Id='SamplePatchFamily' Version='1.0.0.0' Supersede='yes'> <ComponentRef Id='compMainExecutable'/> <ComponentRef Id='compManual'/> </PatchFamily> </Patch> </Wix>
Classification屬性可以是: Hotfix, Security Rollup, Critical Update, Update, Service Pack or Update Rollup。AllowRemoval 屬性確定用戶在以後是否能夠卸載該補丁包。
PatchFamily 標記包含要被修補的項目,Supersede決定是否目前的補丁包替換掉相同的系列裏的所有以前的補丁包。PatchFamily 的子元素可以是ComponentRef或者FeatureRef等是對Sample.wxs文件中的元素引用,表示版本間不同的地方
5、使用另外一個wix工具torch在兩個安裝包之間來創建一個transform文件。命令行參數-xi指示程序使用wix自己的格式.wixpdb 和.wixmst,而不是Windows Installer格式 (.msi and .mst)。
torch.exe -p -xi 1.0.0/Sample.wixpdb 1.0.1/Sample.wixpdb -out Patch.wixmst
candle.exe Patch.wxs light.exe Patch.wixobj
pyro.exe Patch.wixmsp -out Patch.msp -t SamplePatch Patch.wixmst
生成的patch.msp就是修補安裝程序,爲了測試它,我們先安裝原始安裝包(Error/Product.msi),然後添加修補程序:
msiexec /p Patch.msp
檢查文件是否真的被更新成新版本了。接着到添加/刪除程序,選擇顯示更新,並移除第一個補丁,所做的更改將恢復到更新前的狀態。
這裏介紹的只是1對1版本的升級,也可以同時爲多個不同的舊版本製作最終版本的升級包,有興趣的可以參考sdk文檔。下一章我們將介紹如何添加系統必備組件的安裝程序。