Wix打包系列 (六)製作升級和補丁包

 前面我們已經知道怎麼製作一個完整安裝包了,但我們的軟件往往不能一次性就滿足客戶的需要,當客戶需要我們給軟件進行升級的時候,我們應該怎麼做呢?

 

    在這之前,我們有必要了解下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 GUIDUpgradeCode屬性仍然保持不變。這種方式允許我們添加新的featurescomponents,但是不能改變feature-component樹的組織結構。Minor upgradesSmall 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);這裏的UpgradeVersionProperty屬性,相當於定義了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,設置它的conditionUPGRADINGPRODUCTCODE 屬性的值。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>
 
    6.3 製作升級補丁(Minor upgrades And Small updates

 

    到這裏大家應該知道爲什麼我說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 upgradesSmall updates
  • 當你更改了Packge裏任何Component的GUID時,不能使用Minor upgradesSmall updates
  • 當有組件被移除時,不能使用Minor upgradesSmall updates
  • 當更改了Feature的組織結構,比如在Feature中添加或刪除子Feature時,不能使用Minor upgradesSmall 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.wixobj 
   4、創建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
    注意執行torch命令所在文件夾不能包含中文,否則執行會出現文件不存在的錯誤
    6、使用wix編譯器和連接器生成補丁包,但是這次輸出格式跟往常不一樣,是.wixmsp格式的。在這裏不需要告訴linker生成這種類型的文件,linker將根據源文件的內容自動決定生成什麼格式的文件。
candle.exe Patch.wxs
light.exe Patch.wixobj
      
    7、最後,我們用上一步生成的wixmsp文件和剛纔生成的transform文件來創建目前的Windows Installer 補丁包。Pyro是負責生成補丁包的wix工具,它不僅需要transform文件名,還需要相應的PatchBaseline/@Id屬性,最好是用命令行模式:
pyro.exe Patch.wixmsp -out Patch.msp -t SamplePatch Patch.wixmst

生成的patch.msp就是修補安裝程序,爲了測試它,我們先安裝原始安裝包(Error/Product.msi),然後添加修補程序:

msiexec /p Patch.msp

檢查文件是否真的被更新成新版本了。接着到添加/刪除程序,選擇顯示更新,並移除第一個補丁,所做的更改將恢復到更新前的狀態。

 

    這裏介紹的只是1對1版本的升級,也可以同時爲多個不同的舊版本製作最終版本的升級包,有興趣的可以參考sdk文檔。下一章我們將介紹如何添加系統必備組件的安裝程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章