天天看點

Wix打包系列(三)自定義Action(Custom Action)

   3.1 關于Action

    我們已經知道如何生成具有标準安裝界面的安裝程式了,Windows Installer按照我們的界面設定使用标準的安裝步驟進行安裝,它的安裝過程是由一系列标準的Action組成,通過這些Action來完成對計算機的安裝配置;如果我們想自定義安裝步驟或者在安裝過程中執行自定義的操作,就需要使用自定義的Action。當然,使用Custom Action之前,我們應該先了解一下msi中這些标準的Action。

    首先,我們通過Orca工具(參考Windows Installer SDK)打開Sample.msi檔案,可以看到使用wix标準安裝界面生成的安裝程式包含哪些Action,如下圖,我們可以看到這些Action的名稱和安裝順序,關于Orca以及msi資料庫的表和字段的含義,本文不做介紹,有興趣可以檢視msi sdk

Wix打包系列(三)自定義Action(Custom Action)

    這裡我們主要關注InstallExecuteSequence 和InstallUISequence表,他們之間的差別是InstallUISequence隻有在内置UI的等級是Full UI或者reduced UI時才會起作用,在basic UI 或者silent方式安裝時不起作用;關于UI等級 在msi sdk中UILevel Property章節有詳細說明。我們定義的Custom Action也可能會包含在這些   Action序列中,通過Sequence來确定執行順序;當然也可能不包含在Action序列中,當這個Action是由按鈕觸發的時候。

    3.2 Custom Action

    下面我們來看看怎麼使用Custom Action,我們在代碼中Product标簽下加入如下代碼:

<Property Id='NOTEPAD' Value='Notepad.exe'/>
<CustomAction Id='LaunchFile' Property='NOTEPAD' ExeCommand='[INSTALLDIR]Manual.pdf' Return='asyncNoWait' />
<InstallExecuteSequence>
    <Custom Action='LaunchFile' After='InstallFinalize'>NOT Installed</Custom>
</InstallExecuteSequence>      

    添加以上代碼的作用是安裝結束後使用記事本打開安裝目錄下的 Manual.pdf檔案,代碼很好了解 CustomAction标記訓示要執行的動作,而 InstallExecuteSequence訓示Action執行的順序,在InstallFinalize之後執行,NOT Installed是安裝的條件,指的是隻有在首次安裝的時候才會運作此Action,維護模式時不執行Action。    wix中使用屬性來區分Action的種類,對于不同種類的Action需要使用不同的屬性組合;而msi中使用Type來區分,具體可參考 http://msdn.microsoft.com/library/aa368062.aspx。    假如我們要安裝後直接運作應用程式,CustomAction可以做如下調整:

<CustomAction Id='LaunchFile'  FileKey='filFoobarEXE' ExeCommand='' Return='asyncNoWait' />      

3.3 Control Action

    3.2中的LaunchFile動作在InstallExecuteSequence序列中的InstallFinalize動作之後執行,即便InstallFinalize已經在InstallExecuteSequence的最後了,但是LaunchFile仍然會在完成界面彈出之前執行;一般我們都想在結束界面點選完成按鈕之後執行LaunchFile動作,這時我們就需要用到按鈕的事件了,把CustomAction動作綁定到按鈕的事件中,這樣點選結束按鈕就可以執行LaunchFile了,操作步驟如下:     首先,同樣添加CustomAction:

<CustomAction Id='LaunchFile'  FileKey='filFoobarEXE' ExeCommand='' Return='asyncNoWait' />      

    然後,我們可以查到WixUI_Mondo模式的結束視窗是ExitDialog,需要給ExitDialog界面的完成按鈕添加事件:

<UI Id="MyWixUI_Mondo">
    <UIRef Id="WixUI_Mondo" />
    <UIRef Id="WixUI_ErrorProgressText" />

    <Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchFile" Order="1">1</Publish>
</UI>      

    Publish是用來定義按鈕的事件, Control是按鈕的辨別; Event屬性是事件類型,這裡我們隻介紹DoAction類型; Value的值就是CustomAction的辨別;一個按鈕可以定義多個Publish事件,Order的值是設定事件執行的順序,ExitDialog本身沒有定義其他事件,這裡可以定義為1;如果不知道預設界面的按鈕有哪些事件,可以用Orca工具打開msi檔案,通過檢視ControlEvent表可以知道所有按鈕的事件和順序。     另外要注意的是:Publish标簽必須定義在Control或者UI的下級,是以這裡我們定義了一個自定義的UI标簽MyWixUI_Mondo。     關于不同Type的Action應用還有很多,感興趣的可以看wix和msi sdk的文檔,下面我們主要介紹如何編寫托管的Custom Action。    

    3.4 編寫托管的Custom Action

    比如我們要在安裝之前檢查某個資料庫連接配接是否存在,上面的CustomAction設定則不能滿足我們的要求,這時我們就需要自己寫一個Action 的DLL。這裡我們隻介紹如何使用c#生成Action ,對于使用VB和c++語言則不做介紹。

    如果我們在安裝wix之前安裝了vs2005或者vs2008,我們就可以很友善的在vs開發環境下生成Action ,首先在解決方案資料總管中右鍵解決方案名稱,然後選擇添加項目,在添加新項目對話框中項目類型選擇wix,模闆選擇c# Custom Action Project,如圖:

Wix打包系列(三)自定義Action(Custom Action)

    下面我們在SampleCustomAction類中添加一個連接配接資料庫的方法,代碼如下:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;

namespace SampleCustomAction
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult ConnectDataBase(Session session)
        {
            session.Log("Begin ConnectDataBase");

            // 資料庫連接配接串
            string connectionStr = session["CONNECTIONSTRING"];

            // 連接配接資料庫方法
            //if (DBUtil.ConnectDB(connectionStr))
                session["CONNECTSUCCESS"] = "1";
            //else
                session["CONNECTSUCCESS"] = "0";

            return ActionResult.Success;
        }
    }
}
      

    這裡方法實作部分省去了,讀者可以自己去實作,資料庫連接配接串CONNECTIONSTRING是在wix源檔案中定義的:

<Property Id="CONNECTIONSTRING" Value="data source=(local);user=sa;password=123;initial catalog=master;Persist Security Info=;" />      

    然後我們編譯SampleCustomAction,在輸出目錄下會生成2個dll,要注意隻有*.CA.dll才是我們需要的,也就是SampleCustomAction.CA.dll,将SampleCustomAction.CA.dll複制到wix源檔案所在目錄,在Sample.wxs檔案中添加如下行:

<Binary Id='ConnectDBClass' SourceFile='$(var.Version)/SampleCustomAction.CA.dll' />

<CustomAction Id='ConnectDB' BinaryKey='ConnectDBClass' DllEntry='ConnectDataBase' />
<CustomAction Id='ConnectError' Error='資料庫連接配接失敗' />

<InstallExecuteSequence>
    <Custom Action='ConnectDB' After='CostFinalize' />
    <Custom Action='ConnectError' After='ConnectDB'><![CDATA[CONNECTSUCCESS <> "1" AND NOT Installed]]></Custom>
 </InstallExecuteSequence>      

     第1行的 Binary 就是我們編寫的dll檔案,第3行是CustomAction的入口, BinaryKey值是第1行定義的CustomAction的dll辨別, DllEntry值是dll中CustomAction的入口函數,第4行是另外一種Type類型的CustomAction,它彈出錯誤對話框,然後終止安裝程式; InstallExecuteSequence中定義Action的執行順序和執行條件,第8行中條件表明隻有 CONNECTSUCCESS= "0" AND NOT Installed時此Action才會執行,也就是連接配接資料庫失敗并且首次安裝時執行。     注意到這裡CustomAction的 BinaryKey屬性表明CustomAction檔案被打包進安裝檔案中以二進制形式存在,不會被安裝到目标計算機硬碟上;如果CustomAction檔案是安裝到目标計算機硬碟上的檔案,則應使用FileKey屬性,并添加相應的File标記。     編譯源檔案,運作生成的安裝包,安裝過程中會彈出錯誤對話框“資料庫連接配接失敗”;托管的Action調試起來不友善,可以自己寫一個輔助的程式調試,也可以通過session.Log方法記錄安裝日志,通過以下指令行方式運作安裝程式可以生成安裝日志:

msiexec /i 1.0.0/Sample.msi /l*v Sample.msi.log      

      打開安裝日志,我們可以看到Action執行的日志:

...
...
操作結束 15:24:05: CostFinalize。傳回值 1。
MSI (s) (E0:BC) [15:24:05:218]: Doing action: ConnectDB
操作 15:24:05: ConnectDB。
操作開始 15:24:05: ConnectDB。
MSI (s) (E0:F4) [15:24:05:218]: Invoking remote custom action. DLL: C:/WINDOWS/Installer/MSI1A1.tmp, Entrypoint: ConnectDataBase
MSI (s) (E0:04) [15:24:05:218]: Generating random cookie.
MSI (s) (E0:04) [15:24:05:250]: Created Custom Action Server with PID 4108 (0x100C).
MSI (s) (E0:24) [15:24:05:281]: Running as a service.
MSI (s) (E0:24) [15:24:05:281]: Hello, I'm your 32bit Impersonated custom action server.
SFXCA: Extracting custom action to temporary directory: C:/WINDOWS/Installer/MSI1A1.tmp-/
SFXCA: Binding to CLR version v2.0.50727
Calling custom action SampleCustomAction!SampleCustomAction.CustomActions.ConnectDataBase
Begin ConnectDataBase
MSI (s) (E0!F4) [15:24:05:687]: PROPERTY CHANGE: Adding CONNECTSUCCESS property. Its value is '1'.
MSI (s) (E0!F4) [15:24:05:687]: PROPERTY CHANGE: Modifying CONNECTSUCCESS property. Its current value is '1'. Its new value: '0'.
操作結束 15:24:05: ConnectDB。傳回值 1。
MSI (s) (E0:BC) [15:24:05:796]: Doing action: ConnectError
操作 15:24:05: ConnectError。
操作開始 15:24:05: ConnectError。
資料庫連接配接不存在
MSI (s) (E0:BC) [15:24:06:703]: 産品: Foobar 1.0 -- 資料庫連接配接不存在

操作結束 15:24:06: ConnectError。傳回值 3。
操作結束 15:24:06: INSTALL。傳回值 3。
...
...      

    這裡CONNECTSUCCESS 屬性在wxs源檔案中并沒有定義,是以在執行到“session[ "CONNECTSUCCESS"] = "1";”時,會添加該屬性,如果wxs源檔案中已定義,該處就會修改該屬性值。       如果沒有vs編譯環境,也可以使用wix提供的MakeSfxCA工具編譯生成dll,使用示例如下,具體可以參考wix文檔和DTF文檔:

1: csc.exe /target:library /reference:path/Microsoft.Deployment.WindowsInstaller.dll /out:CheckPID.dll CheckPID.cs      
2: MakeSfxCA.exe path/CheckPIDPackage.dll path/sfxca.dll path/CheckPID.dll path/CustomAction.config path/Microsoft.Deployment.WindowsInstaller.dll      

    到這裡我們已經知道如何建立自定義的Action了,下一節我們将介紹如何自定義對話框,在安裝程式标準UI的基礎上定義我們自己的對話框和UI流程