自定義MSI 安裝

深入瞭解在 Visual Studio .NET 中創建安裝例程的基礎知識!Vishnu Prasad H 探究了安裝項目模板、編輯器、自定義安裝程序以及更多內容。然後,他將所有內容放在一起,組成了一個部署數據庫應用程序的示例。

與以前相比,現在部署 .NET 應用程序非常簡單。現在 xcopy 部署也成爲了可能。但是,簡單 xcopy 部署還是有限制的。它不能用於分層應用程序部署、配置設置、細化調整等等。將應用程序層與自定義配置集成在一個分佈式框架中需要專業、可自定義的部署工具。基於 Microsoft Windows Installer (MSI) 的部署能夠完成所有這些任務甚至更多任務。然而,要使其得以運行還需要一些努力。幸運的是,Visual Studio .NET 的安裝和部署項目使這變得相當簡單。

部署示例

我創建了一個示例應用程序,以演示 MSI 部署在 Visual Studio .NET 中的功能。這是一個非常簡單的應用程序,只有一個顯示客戶的窗體。

該應用程序具有一個 CustomSteps 類庫。此庫包括一個 SetupDB 組件類,它從 Installer 類繼承。該組件類提供了自定義數據庫安裝。它對 OSQL 進程進行了自定義,並運行該進程以安裝和刪除數據庫。我講述的重點主要在這個類庫。

然後,您就擁有了 Customer 安裝,這是一個安裝項目。此項目提供了與用戶進行接口的組件,可用來捕獲諸如服務器名、數據庫名、用戶 ID 等數據庫信息。然後,它會將這些文件複製到目標,並創建數據庫、數據庫的對象和基礎數據。最後,它會在 CustomSteps 類庫的幫助下對配置文件中的連接字符串屬性進行配置。

創建示例應用程序

即使您打算在閱讀本文的同時着手創建該示例,也請您去下載本文附帶的源代碼。(創建這個基本的 Customer 應用程序沒有什麼要指出的內容,因爲包括它的目的只是爲了最終了解安裝程序如何運行!)

創建一個空的解決方案,將其命名爲 InstallerSample,然後將其添加到下載的 Customer 應用程序中。此應用程序只有一個窗體,其中帶有一個顯示數據庫中客戶的數據網格,如圖 1 所示。有一個 Resources 文件夾,其中的 Customers.xml 文件配置了一些數據。請注意,該文件的 Build Action 屬性被設置爲 Content。引入此文件是爲了演示這個 Build Action 的重要性。

App.config 具有下面的配置設置。稍後,您將看到安裝程序在運行時設置的這個值。

<?xml version="1.0" encoding="utf-8" ?>  <configuration>       <appSettings>          <add key="connectionstring" value=""/>       </appSettings>  </configuration>

創建 CustomSteps 庫

此步驟會幫助您處理這個自定義數據庫安裝。在安裝項目中,您可以指定 Custom Actions。此項目的輸出就是其中一個 Custom Action 步驟,因此在文件複製到目標之後稱之爲步驟。

創建一個新的類庫 CustomSteps,添加一個新的 Installer 類,然後將其重命名爲 SetupDB.cs。在進入實施階段之前,您必須決定安裝數據庫的方法。表 1 提供了一些常用方法。

表 1. 數據庫部署方法。
模式 說明

備份還原

SQL Server 提供了備份和還原數據庫的選項。您可以使用 System.Data.SqlClient 命名空間以及特定的數據庫存儲過程進行還原。這可能是最簡單的技術,但是它具有一些問題(如硬編碼的名稱)和其他一些配置考慮因素。

SQL-DMO

如果您打算部署基於 VB/COM/SQL 的應用程序,這就是最好的方法之一。SQL-DMO 提供了封裝的 MS SQL Server 對象,以便用於管理目的。其中包括創建數據庫和其他對象。您可以創建一個帶有 DMO 對象的 EXE,DMO 對象會封裝創建數據庫的操作。

腳本文件

這可能是最好的綜合性方法,因爲它通常都是一種用來維護/管理腳本的最佳做法。您可以使用“OSQL”來執行這些腳本。但是,在轉向部署之前,必須先運行一個有效的 SQL 腳本測試。另外,使用 BCP 實用程序以各種格式遷移大量數據通常也是一個很好的選擇。

ADO.NET

這個選擇的可靠性很好,但是會涉及大量的維護和編碼工作。您可以創建一些 C# 方法來創建數據庫、對象,並使用 SqlClient 命名空間插入語句。

在該示例應用程序中有一個數據庫,它只有兩個表:tblCustomer 和 tblAddress。在此方案中,假設您已經決定採用基於腳本文件的部署。通常,在公司開發環境中,數據庫及其對象是由 DBA 進行緊密控制的,而要求開發人員來維護自己的 DML 語句腳本。在生成時,可以運行一個預先生成的事件來創建 SQL 文件。因此,假設有四個 SQL 文件,如下所示:

DataBase.sql — 用於創建數據庫的腳本。

DropDatabase.sql — 用於刪除數據庫的腳本。

Objects.sql — 表和其他對象的腳本(如果需要的話)。

StandingData.sql — 表的 DML 腳本。

數據庫的名稱應由用戶指定。然後,佔位符可以在運行時替換爲真正的數據庫名稱。在 Database.sql/DropDatabase.sql(請參閱下載中的文件)中,我使用 <> 作爲佔位符。將這些腳本文件複製到項目中一個名爲 Scripts 的單獨子文件夾中。請注意,即使在此處,這些腳本文件的 Build Action 屬性仍然爲 Content。

另外,您可以擁有多個 Objects 和 StandingData SQL 腳本文件;您可能希望該實現足夠靈活,可以處理這種情況。在 Resources 子文件夾下創建 InstallationFiles.xml,並將 Build Action 屬性設置爲 Embedded resource清單 1 顯示了該 XML 文件的內容。

清單 1. InstallationFiles.xml 內容。

<?xml version="1.0" encoding="utf-8" ?> <configroot>  <Files>       <DataBase>              <Add>                   <File name="Database.sql"/>              </Add>              <Remove>                   <File name="DropDatabase.sql"/>              </Remove>       </DataBase>       <Objects>              <File name="Objects.sql"/>       </Objects>       <Records>              <File name="StandingData.sql"/>       </Records>  </Files></configroot></code>

該 XML 文件可幫助您在運行時識別哪些文件用於創建或刪除數據庫及其對象和記錄。您可以在 Files 下面添加多個元素,並使用要執行的 SQL 文件名來配置名稱屬性。

現在,您已經擁有了這些腳本文件,以及一個提供有關每個腳本文件執行哪些操作的信息的 XML 文件。接下來,我要將注意力轉移到 SetupDB.cs。它是從 System.Configuration.Install.Installer 繼承的,後者是所有自定義安裝程序的基類。請記下 Runinstaller(true) 屬性;這樣可以確保調用此 Custom Action。該 Installer 類通過重寫以下四個關鍵方法來爲您提供自定義選項:

Install — 在安裝步驟調用。

Commit — 完成安裝過程。

Rollback — 將系統還原爲安裝前的狀態。在 Install 步驟失敗時調用 Rollback。通常,在重寫該方法時,它應該執行與卸載相同的任務。

Uninstall — 刪除已經安裝的文件,並重置/刪除在安裝過程中完成的任何配置。如果此步驟失敗,通常無法還原到安裝前的狀態。

另外,還有一些前面提到操作的 OnAfter 和 OnBefore 事件。但在此示例中,我只重寫了 Install 和 Uninstall 方法。

首先,我們來講述 Install 方法。此方法必須執行下列步驟:

1.

捕獲並計算用戶輸入的數據庫連接詳細信息。

2.

配置 App.Config 文件 connectionstring 設置中的連接字符串。

3.

以合適的順序運行腳本文件。

首先,將清單 2 中的代碼添加到您的 SetupDb.cs 中。

清單 2. Install 方法的代碼。

public override void Install (   System.Collections.IDictionary stateSaver ){bool TrustedConnection=false;base.Install(stateSaver);try{ if(this.Context!=null)    {StringDictionary parameters = Context.Parameters;    string[] keys =new string[parameters.Count];    parameters.Keys.CopyTo(keys,0);   // Set the StateServer collection values  for(int intKeys=0;intKeys<keys.Length;intKeys++)  {    if(keys[intKeys].Equals("database"))      stateSaver.Add("database",        parameters[keys[intKeys]].ToString());    else if(keys[intKeys].Equals("server"))      {stateSaver.Add("server",        parameters[keys[intKeys]].ToString());}    else if(keys[intKeys].Equals("username"))      {stateSaver.Add("username",        parameters[keys[intKeys]].ToString());}    else if(keys[intKeys].Equals("password"))      {stateSaver.Add("password",        parameters[keys[intKeys]].ToString());}    else if(keys[intKeys].Equals("target"))      {stateSaver.Add("target",        parameters[keys[intKeys]].ToString());}  }  // Evaluate connectionstring based on input  // can encrypt here...  string connectionstring= "Data Source=" +        stateSaver["server"].ToString() ;  connectionstring+= ";Initial Catalog=" +       stateSaver["database"].ToString() ;  if(stateSaver["username"]!=null &&     stateSaver["username"].ToString().Length!=0)  {    // Here you need to test the connection...    // and proceed    SqlConnection conn =       new SqlConnection( "Data Source=" +         stateSaver["server"].ToString() +         ";Initial Catalog=master;User Id=" +         stateSaver["username"].ToString() +         ";Password=" +         stateSaver["password"].ToString());    conn.Open();    conn.Close();    conn.Dispose();    connectionstring += ";User ID=" +       stateSaver["username"].ToString() ;    connectionstring += ";Password=" +       stateSaver["password"].ToString() ;  }  else{    // Here you need to test the connection...    // and proceed.    SqlConnection conn =      new SqlConnection( "Data Source=" +       stateSaver["server"].ToString()+       ";Initial Catalog=master;trusted_connection=yes");    conn.Open();    conn.Close();    conn.Dispose();    TrustedConnection=true;    stateSaver.Add("trustedconnection",true);    connectionstring+=";Trusted_connection=yes";}  // Set the Config file's connection string  XmlDocument doc = new XmlDocument();  doc.Load(stateSaver["target"].ToString()+    @"bin/Customer.exe.config");  XmlNode connectionNode =      doc.SelectSingleNode(    @"configuration/appSettings/" +    @"add[@key='connectionstring']");  if(connectionNode!=null)  {    connectionNode.Attributes["value"].Value =       connectionstring;    doc.Save( stateSaver["target"].ToString()+       @"bin/Customer.exe.config");    EventLog.WriteEntry("SetupDB",      "Configuration file processed...");   }  else  { // This error will ensure installation     // is uncomplete...    throw new       InstallException(      "Configuration file had no node " +      "declared for connection string.");   }  // Run the scripts  DataBaseInstaller dbInstall =null;  if(TrustedConnection)  {    dbInstall= new     DataBaseInstaller(stateSaver["server"].ToString(),      stateSaver["database"].ToString(),      stateSaver["target"].ToString());  }  else{    dbInstall= new       DataBaseInstaller(      stateSaver["server"].ToString(),      stateSaver["database"].ToString(),      stateSaver["username"].ToString(),      stateSaver["password"].ToString(),      stateSaver["target"].ToString());  }  dbInstall.CreateDataBase();  dbInstall.CreateObjects();  dbInstall.CreateRecords();  }}catch(InstallException inst){  throw (inst);}catch(Exception generic){  EventLog.WriteEntry("SetupDB",generic.Message,    EventLogEntryType.Error) ;  throw new InstallException(generic.Message);}}

在清單 2 中,請注意方法的簽名。它有一個名爲 stateSaver 的 IDictionary 參數。它會在 Install、Commit、Rollback 等過程中保持該安裝程序所需的信息。最初,此值爲 null。只有當您調用 base.Install(StateSaver) 時,IDictionary 對象纔會反映過程的當前狀態。因此每個重寫方法都需要調用基類,以便初始化 stateSaver。如果您在各個步驟中執行多個自定義安裝程序,那麼這就變得非常重要。接下來的步驟是獲取通過 Custom Actions 屬性 CustomActionData 傳遞的參數。

StringDictionary parameters = Context.Parameters;

這個 Context 屬性非常重要,因爲它可返回 System.Configuration.Install.InstallContext。甚至在執行 Install 方法之前,該安裝程序就已經設置了安裝程序的 Context 屬性。表 2 指出了 InstallContext 的一些重要成員。

表 2. InstallContext 的一些重要成員。
成員 說明

LogMessage(string)

記錄指定記錄器中的任何消息。該日誌的路徑是在安裝程序的構造函數中指定的。日誌文件的名稱/值參數可以傳遞到自定義安裝程序中。

Parameters

返回包含名稱/值對的 StringDictionary。這是一個重要屬性,它將返回 CustomActionData 中的命令行名稱/值對。

isParameterTrue(string)

如果設置了指定的參數,則返回真,如果沒有設置,則返回假。

清單 2 中的第一部分代碼會檢索不同的鍵值對,並將它們設置到 stateSaver 中。其中包括用戶輸入的數據庫值,如服務器名、數據庫名、用戶 ID、密碼和安裝的目標目錄。當然,在您想要卸載時,這些詳細信息中有一些是至關重要的。您需要將在卸載過程中刪除的數據庫名稱、它的連接字符串等。因此,爲了保持安裝的狀態,所有這些 stateSaver 值都由安裝程序儲存在一個名爲 *.installstate 的文件中。安裝後,您可以在文件夾中查看該文件。按照這種方式使用文件使您能夠避開註冊表。另外,當您構建真正的安裝程序時,最好明智地選擇使用 stateSaver。如果某個用戶在安裝後刪除了此文件,安裝程序在在卸載過程中就會遇到問題。

清單 2 中的第二部分代碼使用用戶輸入的值來檢查數據庫連接。如果用戶沒有輸入用戶 ID,此步驟就會對一個可信連接進行測試。這不是一個用來辨別集成模式驗證選擇或混合模式驗證選擇的簡便方式。但是,現有用戶界面編輯器中的限制又使得必須這樣做。最終,此部分還會爲數據庫創建連接字符串。

清單 2 中的第三部分代碼可在應用程序配置文件中設置連接字符串。此部分會將配置文件加載爲一個 XML 文檔,遍歷到 connectionstring 節點,並設置該值。請注意將該文件的路徑放在一起的方式。

清單 2 中的第四部分代碼通過將運行腳本的任務委託給另一個類,來實際創建該數據庫: DatabaseInstaller。

將 DatabaseInstaller.cs(下載資料中的示例文件)複製到您的項目中。DatabaseInstaller 具有兩個構造函數來傳遞用戶輸入的數據庫詳細信息。另外,它還有一個私有構造函數,如清單 3 所示。

清單 3. 從嵌入式資源加載 InstallationFiles.xml。

XmlDocument config=null;private DataBaseInstaller(){  System.IO.Stream stream =     System.Reflection.Assembly.GetExecutingAssembly().    GetManifestResourceStream(    "CustomSteps.Resources.InstallationFiles.xml");  config=new XmlDocument();  config.Load(stream);}

此構造函數會加載包含有關這些腳本文件詳細信息的 InstallationFiles.xml 文件。您還記得吧,您已經將此文件的 Build Action 顯式設置爲 Embedded resource。之後,您就擁有了封裝創建數據庫功能的各種成員。表 3 提供了有關上述內容的一些詳細信息。

表 3. DatabaseInstaller 的成員。
成員 說明

CreateDataBase()

調用以創建數據庫的公共方法。

DropDataBase()

刪除數據庫的公共方法。

CreateObjects()

創建對象(如表、存儲過程等)的公共方法。

CreateRecords()

在各種表中創建任何基礎數據或配置數據。

GetFullPath

返回帶有腳本文件完整路徑的字符串的私有方法。它採用該腳本文件的名稱。

PopulateDatabaseNamePlaceHolder

使用實際的數據庫名稱替換腳本文件中的佔位符的私有方法。

GetCommonProcessArguments

創建參數的私有方法,這些參數將傳遞到 OSQL 以便創建數據庫。

ExecuteScripts

對於一個給定的 XPath,此私有方法會獲取腳本文件的文件名,並使用 OSQL 對它們進行遞歸執行。這是由 CreateObjects 和 CreateRecords 使用的。

現在我將主要講述 CreateDatabase() 方法,因爲其他大多數方法所遵循的實現都是類似的。清單 4 提供了該方法的主要代碼。

清單 4. 數據庫創建的代碼片段。

ProcessStartInfo processInfo =  new ProcessStartInfo("osql.exe");processInfo.WindowStyle=ProcessWindowStyle.Normal;// Get the name of the file from the assembly's // embedded resource.if(config !=null){  fileName = config.SelectSingleNode(    "configroot/Files/DataBase/Add/File").    Attributes["name"].Value;}else{  //Customized message..  throw new InstallException(    "Configuration for database file " +    "creation missing.");}//Get argumentsprocessInfo.Arguments=  GetCommonProcessArguments(fileName,"master");EventLog.WriteEntry("DatabaseInstaller",  processInfo.Arguments);PopulateDatabaseNamePlaceHolder(  GetFullPath(fileName));Process osql = Process.Start(processInfo);//Wait till it is done...osql.WaitForExit();EventLog.WriteEntry(   "DatabaseInstaller","Database created..");osql.Dispose();return true;

ProcessStartInfo 可用於對您將要啓動的進程(OSQL)進行更好的控制。它爲您提供了一種用於設置參數、控制窗口樣式等的簡便方法。然後,它可以與清單 4 中所示的進程 結合使用。該進程啓動之後,您可以等待它退出,也可以使用 WaitForExit 方法來控制退出時間。另外,您在構造函數中使用初始化的 XmlDocument 來選擇一個預定義的節點,您在其中存儲了數據庫腳本文件的名稱。然後,您將參數構建的過程委託給一個特定的方法 (GetCommonProcessArguments),同時傳遞了該腳本文件的名稱以及要從中運行的數據庫上下文的名稱。在數據庫創建過程中,您可以將 master 作爲數據庫傳遞,原因是您的應用程序數據庫正處於創建過程中。下一行代碼(方法 PopulateDatabaseNamePlaceHolder)使用用戶提供的實際名稱來替換數據庫名稱的佔位符。

請注意,無論何時,只要存在 try、catch 代碼塊或者發生了自定義異常,就會引發一個 InstallException。它的類型爲 SystemException,該安裝程序甚至會在 Commit、Rollback 和 Uninstall 階段引發這種類型的異常。較好的做法是使用 EventLog 或自定義安裝日誌文件來跟蹤並記錄該錯誤。您可以使用 Context 屬性來創建自定義日誌。

您已經爲數據庫創建成功自定義了安裝!

使用相似的方式,您可以重寫 Installer 類的 Uninstall 方法,然後複製附帶示例中的代碼。此時您會看到,當用戶確認時,刪除數據庫的任務就委託給 DatabaseInstaller 類。在接下來的步驟中,您將創建一個安裝項目,並使用它進行部署。

創建安裝項目並進行安裝

最後,您要準備創建安裝項目了。表 4 說明了 VS.NET 提供的各種部署方式(另請參見圖 2)。

表 4. 模板選項。
模板 說明

Setup

生成 MSI。通常在生成基於 Windows 的應用程序時使用。但是,您可以對其進行自定義,以滿足任何形式的部署需要。

WebSetup

爲 Web 部署創建虛擬目錄。還可以使用此模板進行自定義。

Merge Module

它可幫助您將組件與其他應用程序共享,如 .NET Framework、MSDE 等。

Cab

輸出一個壓縮包 (.cab) 文件以便於分發。

對於該示例應用程序,請選擇一個安裝項目模板,並將其命名爲 Customer Setup。

Visual Studio 提供了多種編輯器,用於創建安裝應用程序。我將對其中的一些編輯器及其用法進行探究。

圖 3 顯示的是文件系統編輯器。對於要傳遞到部署環境中的項目、文件等,您可以在這裏添加來自它們的輸出。您還可以選擇專門的文件夾作爲您文件的目標,如 System 文件夾、Program Files 文件夾等。

在 Application 文件夾下創建兩個子文件夾,將其分別命名爲 bin 和 Install。然後選擇 bin 文件夾,右鍵單擊,並選擇“添加”|“項目輸出”,以獲得如圖 4 所示的對話框。

在組合框中選擇“Customer”項目,然後選擇 Primary outputContent files。主輸出表示項目輸出,內容文件將包含標記爲 Build Action 內容的文件。這就是 Build Action 屬性非常關鍵的位置。您還可以複製源文件,但是此示例無需進行復制。

轉至 Install 文件夾,以及 CustomSteps 中的主文件和內容文件。

現在您已經完成了向部署添加提交內容的過程。下一步就是設置用於安裝的用戶界面。

用戶界面編輯器默認情況下,您可以在用戶界面編輯器中看到一組界面,如 target location 等。但是,對於這個應用程序,您需要捕獲四個參數:服務器名、數據庫名、用戶 ID 和密碼。右鍵單擊“Start”部分,然後右鍵單擊“Add Dialog”。選擇爲您提供四個參數的 Textboxes (A) 選項,並按圖 5 所示確定它的位置。此時,您可以配置表 5 中所示的屬性。 表 5. Textboxes (A) 對話框的重要屬性。

屬性 說明

BannerText

要爲該屏幕顯示的標誌。

Edit1Label

第一個文本框的標籤。設置爲 Server Name。查找其他標籤屬性,並相應地進行設置。

Edit1Property

該屬性非常重要:此處提供的名稱要在 Custom Actions 中訪問。將該屬性設置爲 SERVER_NAME。同樣地,爲示例中顯示的其他屬性設置 DATABASE_NAMEUSER_NAMEPASSWORD

Edit1Value

要設置爲默認值的值。您可以指定可用的默認屬性。例如,[ComputerName] 會將此文本框的默認值設置爲用戶計算機的名稱。請檢查示例應用程序中設置的值,以瞭解更多信息。

Edit1Visible

此值有助於您控制文本框的可見性。對於此示例,所有屬性均爲真。

用戶界面編輯器還有一些別的內容。您可以研究它的其他各種對話、它們的屬性,以及一些可用於真正擴展您的自定義的默認 Windows 屬性。但是,對於此示例,我就需要這些,所以我要轉而說明啓動條件編輯器了。

啓動條件編輯器

在某些情況下,您的部署要迎合特定的運行庫,如 .NET Framework、MSDE、MDAC 版本等。如果您沒有什麼先決系統要求,則沒有要指出的內容,繼續進行安裝就可以了。啓動條件編輯器可幫助您完成此任務。在此示例中,除了現有的默認“.NET Framework”條件之外,我還要添加一個條件。單擊啓動條件編輯器,然後右鍵單擊“Search Target Machine”部分。在這裏,您可以評估某個特定的條件。假設此安裝程序是面對 Windows OS 4 和更高版本的。儘管可以使用默認的 Windows 條件,但是我要爲您講述如何創建自己的條件。選擇 Add file registry search,然後按照圖 6 所示設置這些值。

Property 會爲您的搜索評估指定一個名稱。RegKey 是要搜索的目標關鍵字,Root 則是要搜索的註冊表樹的根。這些設置會搜索 Value 集合的 RegKey 路徑(例如 CurrentVersion 值名稱)。此搜索會返回它在 CurrentVersion 值名稱中找到的任何值。

右鍵單擊“Launch Conditions”,然後添加一個新條件。圖 7 提供了此條件的設置。請注意 Condition 屬性。此屬性應該計算爲真,安裝才能繼續。在這裏,您提供了 WINDOWSVERSION>= "4.0"。WINDOWSVERSION 是註冊表搜索條件的名稱。如果返回假,則安裝程序會顯示 Message 屬性中配置的錯誤消息,並中止安裝。您可以按照這種方式配置各種條件,以使您的部署具有條件。

在下一部分中,您將看到如何使用自定義生成的 CustomSteps 安裝程序。

自定義操作編輯器您可以添加屬於部署一部分的任何 EXE、腳本文件或 DLL 文件。在此示例中,您需要添加的只是 CustomSteps 類庫。您可以在 Install 階段添加此類庫。爲此,請右鍵單擊“Install”部分,然後選擇“Add Custom Action”。然後,遍歷 Applications 文件夾/Install,然後選擇主輸出,如圖 8 所示。將該步驟重命名爲 Install database

圖 9 顯示了爲該 Custom Action 配置的屬性。這些屬性中最重要的是 Condition 和 CustomActionData。Condition 可確保此安裝程序只有在滿足特定條件時才運行。CustomActionData 則是向 CustomAction EXE/DLL 傳遞參數。

在此示例中,我按照下列方式設置了這些屬性:

/server=[SERVER_NAME] /target="[TARGETDIR]/" /database=[DATABASE_NAME] /username=[USER_NAME] /password=[PASSWORD] /version=[WINDOWSVERSION]

請注意,設置 Windows 默認屬性(如 TARGETDIR)與設置用戶界面編輯器或啓動條件編輯器中您自己創建的屬性(如 SERVER_NAME 和 WINDOWSVERSION)之間的區別。但是,通用格式爲 /name=[value],後面緊跟反斜槓,具體取決於屬性。另外,請注意 InstallerClass 屬性值。此處傳遞的參數將用作 Context.Parameters。您可以看到這些參數在 CustomSteps 組件中是如何使用的。當您運行該安裝程序時,自定義安裝程序的 Install 方法會在複製了所有部署文件之後被調用。

您又按照類似方法添加了 Uninstall 階段的 Custom Action,並將其重命名爲 Uninstall database。但是,這不需要任何參數。必需的參數將使用 stateSaver 集合提供。這就是爲什麼在安裝過程中創建的 *.installstate 文件非常重要的原因了。您可以在 Install 文件夾中看到該文件。stateSaver 是使用此文件初始化的,因此建議您在安裝過程中向 stateSaver 添加卸載操作所需的所有這些值。

根據您的方案,還可以使用其他一些編輯器,如文件類型編輯器和註冊表編輯器。但是,對於此示例,並不需要這些編輯器。

您現在就可以進行安裝了。生成解決方案和安裝程序項目,然後進行安裝和卸載,以此進行測試。

小結

對於可以使用 VS.NET 中的安裝項目來實現實施的高級概念來說,此處講述的這個過程只是冰山一角。很重要的一點是,要根據您自己的應用程序要求來分析和自定義您的部署。在此示例中,您看到了文件的 Build Action 屬性的用法。

請嘗試減少註冊表項的使用,並處理好最差的情況。請對於所有可能的方案情況(如已刪除的了 *.installstate 文件)測試您的安裝程序,並嘗試以儘可能好的方式來處理這些方案。

另外,在此示例中,我從來沒有檢查過活動數據庫連接。這在卸載過程中是非常重要的。對於應用程序的卸載的重視程度應該給予與安裝一樣重要的重視程度,因爲您肯定會要再次重新安裝該應用程序。

如果現在的 VS.NET 安裝編輯器有限制的話,那麼這個限制也會存在於用戶界面編輯器中。我相信對於將來版本的 VS.NET 來說,最佳的解決方案是爲開發人員提供一個可在設計時添加自己的用戶界面的選項,並且帶有用於對話框的條件選項。但是,目前您僅限於了 Custom Actions。

異常處理也非常重要,因爲總是應該在回滾/卸載之後保持安裝前的狀態。

不要對於目標運行庫時環境做作任何假設,。而是始終總是要使用啓動條件來驗證是否存在您需要的每項內容是否存在。

最後,請一定要生成適合於您的應用程序的安裝程序。此過程值得您花費項目時間和成本。

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