在WIX中,CustomAction用來在安裝過程中執行自定義行為。比如注冊、修改文件、觸發其他可執行文件等。這一節主要是介紹一下CustomAction的7種用法。 在此之前要了解InstallExecuteSequence,它是一個Action的執行序列。 Installer會按照默認順序來執行這些Action。通過字面意思也大概知道這些Action的目的。這些方法不是每次一都執行,分安裝和卸載。如果CustomAction沒有指定,很可能會安裝卸載的時候都會執行一次。
• AppSearch
• LaunchConditions
• ValidateProductId
• CostInitialize
• FileCost
• CostFinalize
• InstallValidate
• InstallInitialize
• ProcessComponents
• UnpublishFeatures
• RemoveRegistryValues
• RemoveShortcuts
• RemoveFiles
• InstallFiles
• CreateShortcuts
• WriteRegistryValues
• RegisterUser
• RegisterProduct
• PublishFeatures
• PublishProduct
• InstallFinalize
一、設置變量
Custom Action本身是分很多類型,這里是用到的是Type 51 Custom Action。用來設置變量的值。詳情請看 Custom Action Types
<CustomAction Id="rememberInstallDir" Property="ARPINSTALLLOCATION" Value="[INSTALLLOCATION]" />
這個例子用內置的ARPINSTALLLOCATION變量保存了安裝路徑,value是用方括號包括目錄ID。同樣的方式你可以設置你的自定義變量。然后加入執行序列。
<InstallExecuteSequence> <Custom Action="rememberInstallDir" After="InstallValidate"/>
</InstallExecuteSequence>
我們可以獲取這些變量,比如在卸載的時候通過C#程序獲取變量的值,稍后會介紹。
ProductInstallation install =new ProductInstallation(session["ProductCode"]);
string installDir = install.InstallLocation;
還有第二種方法,Custom Action的簡便方式:使用SetProperty 元素 ,例如在InstallInitialize 執行完成后我們將自定義的變量MyDir 的值設置成123.
<SetProperty Id="MyDir" Value="123" After="InstallInitialize" Sequence="execute" />
記錄日志:
session.Log("installDir:"+installDir); session.Log("ARPINSTALLLOCATION:" + session["ARPINSTALLLOCATION"]); session.Log("MyDir:" + session["MyDir"]);

奇怪Install.InstallLocation方式獲取到的installDir為空(看來書上的有問題),但直接獲取變量,我們發現ARPINSTALLLOCATION和MyDir確實被賦值了。
二、設置安裝目錄
類型35的自定義Action用來設置一個Directory元素的路徑。示例:
<CustomAction Id="SetAppDataDir" Directory="DataDir" Value="[CommonAppDataFolder]MyProduct" />
那這個句子會將ID為DataDir的目錄在XP系統下設置為“C:\Documents and Settings\All Users\Application Data”,WIN7下的 C:\ ProgramData 然后這個Action需要在InstallFiles之前執行。
同樣這也有第二種方法設置Directory的值。
<SetDirectory Id="DataDir" Value="[CommonAppDataFolder]MyProduct" Sequence="execute" />
加入序列:
<InstallExecuteSequence> <Custom Action="SetAppDataDir" Before="InstallFiles" /> </InstallExecuteSequence>
記錄日志:(Directory 本質也是Property)
session.Log("DataDir:" + session["DataDir"]);

結果正確。
三、運行嵌入的VBScript和JScript
類型37和類型38的Custom Action可以執行嵌入的腳本。通過Script屬性來指定是vbscript還是jscript. 下面的示例是彈出兩個提示框。
<CustomAction Id="testVBScript" Script="vbscript" Execute="immediate" > <![CDATA[ msgbox "this is embedded code..." msgbox "myProperty: " & Session.Property("myProperty") ]]> </CustomAction>
注意到可以通過Session.Property的方式獲取到已經存在的變量。 這個session對象控制着安裝進程,它打開了包含安裝表格和數據的的數據庫。從而可以通過這個上下文對象可以獲取和修改安裝的變量和參數。更多信息可以參考 Session Object
加入序列
<InstallUISequence> <Custom Action="testVBScript" After="LaunchConditions" /> </InstallUISequence>
<Property Id="myProperty" Value="2" />
執行結果:

下面是一些有用的示例:
<CustomAction Id="testVBScript" Script="vbscript" Execute="immediate" > <![CDATA[ ' 寫入一條安裝日志 Dim rec Set rec = Session.Installer.CreateRecord(0) rec.StringData(0) = "My log message" Session.Message &H04000000, rec '改變安裝level Session.SetInstallLevel(1000) ' 獲取Product元素的屬性。 Dim productName productName = Session.ProductProperty("ProductName") ' 改變Directory元素的路徑 Dim installFolder installFolder = Session.TargetPath("INSTALLFOLDER") ]]> </CustomAction>
四、調用外部的腳本文件(VBScript / JScript)
類型5(JScript)和類型6(VBScript)的自定義Action可以調用外部腳本文件的函數。我們定義一個myScript.vbs的文件,它包含一個myFunction的函數:
Function myFunction() If Session.Property("myProperty") = "2" Then msgbox "Property is 1. Returning success!" myFunction = 1 Exit Function End If msgbox "Property not 1. Returning failure." myFunction = 3 Exit Function End Function
返回1意味着調用成功,返回3意味着調用失敗,而且會終止安裝。另外我們需要用一個Binary元素來保存我們的vbs文件。
<Binary Id="myScriptVBS" SourceFile="myScript.vbs" />
然后通過我們的CustomAction和的BinaryKey和VBScriptCall(或者JScriptCall如果是JScript文件)來調用函數:
<CustomAction Id="myScript_CA" BinaryKey="myScriptVBS" VBScriptCall="myFunction" Execute="immediate" Return="check" />
加入序列:
<InstallUISequence> <Custom Action="myScript_CA" After="LaunchConditions" /> </InstallUISequence>

五、調用動態鏈接庫中的方法
類型1的自定義Action可以調用dll中的方法,VS提供了下面的模板。

我們將編寫一個C#的dll,技術上講,類型1的Custom Action需要非托管的C/C++的dll,Windows Installer 本身不支持.Net下的自定義Action。不過我們使用的模板會將我們的托管代碼編譯成c/c++的dll。使用.Net的時候要注意.Net的版本,當然也有C++的Custom Action 供選擇。
創建一個C# Custom Action Project 之后,我們會得到一個引用了Microsoft.Deployment.WindowsInstaller 的源文件。這個命名空間可以讓我們獲取properties ,features 和 components 。
using Microsoft.Deployment.WindowsInstaller; namespace CustomAction { public class CustomActions { [CustomAction] public static ActionResult CustomAction1(Session session) { session.Log("Begin CustomAction1"); return ActionResult.Success; } } }
ActionResult 用來說明custom action 的調用是成功還是失敗。單個的.Net 程序集之前只能定義16個CustomAction。wix3.6以后提高到128個。如果超過了這個,你只能再定義一個程序集了(沒有測試過...)。這些都是來自Deployment Tools Foundation(DTF)的支持,DTF是一個讓我們寫.Net代碼的類庫,並且支持低級的Windows Installer技術。它提供了很多有用的類。 工程編譯之后,我們會得到兩個文件,一個是.dll 另外一個是 .CA.dll ,只有后者的才能被MSI識別。
有兩種方法引用這個dll。
1.直接復制到Wix 工程目錄,然后用Binary 元素引用它。
<Binary Id="myCustomActionsDLL" SourceFile="CustomAction.CA.dll" />

2.就是引用工程,再用變量指向工程的輸出目錄。

<Binary Id="myCustomActionsDLL" SourceFile="$(var.CustomAction.TargetDir)CustomAction.CA.dll" />
示例:在安裝結束后,修改應用程序中的配置參數
public class CustomActions { [CustomAction] public static ActionResult SetDevLanguage(Session session) { Record record = new Record(0); record[0] = "開始執行!"; session.Message(InstallMessage.Info, record); session.Log("Begin CustomAction SetDevLanguage"); string xmlDoc = Path.Combine(session["INSTALLFOLDER"], "DVStudio.exe.config"); session.Log("安裝目錄:" + session["INSTALLDIR"]); session.Log("配置文件:" + xmlDoc); if (File.Exists(xmlDoc)) { session.Log("config文件存在!"); session.Log("安裝包語言是" + session["ProductLanguage"]); string strContent = File.ReadAllText(xmlDoc); strContent = Regex.Replace(strContent, "zh-CHT", lanDictionary[session["ProductLanguage"]]); session.Log("語言修改為" + lanDictionary[session["ProductLanguage"]]); File.WriteAllText(xmlDoc, strContent); } else { session.Log("config文件沒有找到!"); } return ActionResult.Success; } private static Dictionary<string, string> lanDictionary = new Dictionary<string, string>() { {"2052","zh-CN"}, {"1028","zh-CHT"}, {"1033","en-US"}, }; }
定義Custom Action 並加入序列
<CustomAction Id="CA_myCustomAction" BinaryKey="myCustomActionsDLL" DllEntry="SetDevLanguage" Execute="immediate" Return="check" />
<Custom Action="CA_myCustomAction" After="InstallFiles" Overridable="yes" />
測試發現,after InstallFiles有的時候並不能檢測到config文件,可能是安裝文件和執行Action存在一定的延時,修改為InstallFinalize(最后一個Action)后,沒有出現找不到文件的情況。

另外需要說明的是Session.Message 類型為Info的時候只是記錄到日志,修改為Warning會彈出提示框。
六、觸發可執行文件
有兩種方法可以通過custom Action運行一個可執行文件(.exe)。類型2的Custom Action是用Binary元素來保存文件。類似上面的腳本文件的做法。所有這些Binary中的文件都不會拷貝到用戶的電腦上去。我們創建一個exe並放到工程目錄下。
<Binary Id="myProgramEXE" SourceFile="$(sys.SOURCEFILEDIR)Demo.exe" />
這里的$(sys.SOURCEFILEDIR)是WIX定義的系統變量,代表的是工程目錄。
定義一個CustomAction:
<CustomAction Id="myProgramEXE_CA" BinaryKey="myProgramEXE" Impersonate="yes" Execute="deferred" ExeCommand="" Return="check" />
CustomAction的Impersonate屬性告訴Installer是否模擬用戶去觸發這個Exe,它的默認值是no,意味着以LocalSystem 用戶(功能強大的內置賬戶,擁有改變用戶電腦參數的權限)去執行。如果你不需要,就設置為Yes,以當前用戶的身份去運行。而ExeCommand屬性可以傳遞命令行參數到可執行文件。即使是內容為空,也需要定義它。
然后加入序列:
<InstallExecuteSequence> <Custom Action="myProgramEXE_CA" Before="InstallFinalize" /> </InstallExecuteSequence>
另外一種方式是類型18的Custom Action是直接調用的安裝了的文件。
<DirectoryRef Id="INSTALLLOCATION"> <Component Id="CMP_MainAppEXE" Guid="7AB5216B-2DB5-4A8A-9293-F6711FFAAA83"> <File Id="mainAppEXE" Source="Demo.exe" KeyPath="yes" /> </Component> </DirectoryRef>
而Custom action 通過ID獲取到文件
<CustomAction Id="RunMainApp" FileKey="mainAppEXE" ExeCommand="" Execute="commit" Return="ignore" />

如果不想運行的程序顯示界面,也就是靜默執行,需要使用WixUtilExtension下的QtEexc action .WIX文檔中也有這么一節: Quiet Execution Custom Action
另外類型34的Custom action 可以通過目錄獲取文件,並傳遞了參數。
<CustomAction Id="RunMainApp" Directory="INSTALLLOCATION" ExeCommand="[INSTALLLOCATION]Main_App.exe –myArg 123" Execute="commit" Return="ignore" />
七、發送錯誤終止安裝
類型19的自定義action使用Error屬性,來發送一個錯誤並終止安裝。
<Property Id="myProperty" Value="0" /> <CustomAction Id="ErrorCA" Error="Ends the installation!" /> <InstallUISequence> <Custom Action="ErrorCA" Before="ExecuteAction"> <![CDATA[ myProperty <> 1 ]]> </Custom> </InstallUISequence>
在Custom元素內部有一個條件表達式,當myproperty不為1的時候觸發。(注意到這個action是在InstallUISequence中觸發的)
小結:CustomAction給我們提供了可以自己編程的窗口,可以去觸發bat,修改配置文件,觸發exe。文章大部分內容是來自《Packtpub.WiX.3.6.A.Developers.Guide.to.Windows.Installer.XML.Dec.2012》的翻譯和測試。希望對你有幫助。
