1. 程式人生 > >使用WiX Toolset建立.NET程式釋出Bootstrapper(安裝策略管理)(二)——自定義安裝

使用WiX Toolset建立.NET程式釋出Bootstrapper(安裝策略管理)(二)——自定義安裝

自定義產品解除安裝方式

        繼續從上一次的基礎上前進,現在我們已經知道了最簡單的bootstrapper打包方法,現在我們對其中的每個節點深入自定義,爭取可以達到我們需要的效果。先把最後全部的XML貼出來。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" 
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
  <Bundle Name="CamCard" Version="4.0.0.0" Manufacturer="IntSig Information Co., Ltd." AboutUrl="http://www.intsig.net" IconSourceFile="icon_256.ico"  
          UpgradeCode="1EB9EC76-9E5F-4471-B522-314A62518A80" DisableRemove="no" DisableModify="yes" DisableRepair="yes">
    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
      <bal:WixStandardBootstrapperApplication LicenseFile="license.rtf" ThemeFile="MyTheme.xml" LocalizationFile="MyLocalize.wxl" LogoFile="logo.png" />
    </BootstrapperApplicationRef>
    <Chain>
      <ExePackage Id="Netfx4Full"
          Cache="no"
          Compressed="no"
          PerMachine="yes"
          Permanent="yes"
          Vital="yes" InstallCommand=" /q /norestart"
          SourceFile="dotNetFx40_Full_x86_x64.exe"
          DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=164193"
          DetectCondition="DotNetFramework40FullInstallRegValue=1" />
      <MsiPackage Compressed="no" SourceFile="SSCERuntime-CHS.msi" Vital="yes" DisplayInternalUI="no" Permanent="yes" ForcePerMachine="yes"
          DownloadUrl="http://go.microsoft.com/fwlink/?LinkID=166085"
          InstallCondition="VersionNT = v5.1" />
      <MsiPackage Compressed="no" SourceFile="IntSig.CamCard.Installer.msi" Vital="yes" DisplayInternalUI="no" Permanent="no"  ForcePerMachine="yes">
        <MsiProperty Name="TARGETDIR" Value="[InstallFolder]"/>
      </MsiPackage>
    </Chain>
    <util:RegistrySearch Id="FindDotNet40FullInstallRegValue" Root="HKLM"
                 Key="SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" Value="Install"
                 Variable="DotNetFramework40FullInstallRegValue" />
  </Bundle>
</Wix>

        Bundle節點前面幾個屬性我們都已經知道了,IconSourceFile就是打包後exe的圖示設定,DisableRemove、DisableModify這兩個屬性比較有講究,他們分別設定了在“新增/刪除程式”列表中,選中安裝包後滑鼠右擊,是否會出現“解除安裝”和“修改”這兩個選項。如果這兩個選項都同時為yes,那這個產品安裝後根本就不會出現在“新增/刪除程式”列表中,只能通過再次雙擊bootstrapper安裝exe進行解除安裝。

自定義產品安裝介面

    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
      <bal:WixStandardBootstrapperApplication LicenseFile="license.rtf" ThemeFile="MyTheme.xml" LocalizationFile="MyLocalize.wxl" LogoFile="logo.png" />
    </BootstrapperApplicationRef>

        BootstrapperApplicationRef這個節點上一篇已經講解過,但是我們使用的是最原始的預設介面,事實上可以通過在該節點中加入WixStandardBootstrapperApplication來自定義安裝介面。LicenseFile就是要顯示在使用者安裝協議中的RTF檔名稱。ThemeFile是自定義主題xml檔案,該檔案詳細定義了安裝介面中的每個按鈕和控制元件的位置。LocalizationFile是本地化配置檔案,這個版本的Burn框架還不支援執行時自動根據安裝語言環境自動切換。如果你需要採用本地化安裝策略,比較靠譜的方法就是在bootstrapper之前再執行另一個exe,用來判斷語言環境並自動執行不同的bootstrapper進行安裝。LogoFile是安裝介面左上角那個圖示,注意XP環境下好像是不能使用ICON檔案的。

        ThemeFile="MyTheme.xml" 這個檔案可以在WiX原始碼中找到,我貼在這篇部落格裡面,方便大家直接複製貼上,這檔案和後面的本地化檔案的確不好找。

<?xml version="1.0" encoding="utf-8"?>
<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
  <Window Width="485" Height="300" HexStyle="100a0000" FontId="0">#(loc.Caption)</Window>
  <Font Id="0" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
  <Font Id="1" Height="-24" Weight="500" Foreground="000000">Segoe UI</Font>
  <Font Id="2" Height="-22" Weight="500" Foreground="666666">Segoe UI</Font>
  <Font Id="3" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
  <Font Id="4" Height="-12" Weight="500" Foreground="ff0000" Background="FFFFFF" Underline="yes">Segoe UI</Font>

  <Image X="11" Y="11" Width="64" Height="64" ImageFile="logo.png" Visible="yes"/>
  <Text X="80" Y="11" Width="-11" Height="64" FontId="1" Visible="yes" DisablePrefix="yes">#(loc.Title)</Text>

  <Page Name="Help">
    <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.HelpHeader)</Text>
    <Text X="11" Y="112" Width="-11" Height="-35" FontId="3" DisablePrefix="yes">#(loc.HelpText)</Text>
    <Button Name="HelpCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.HelpCloseButton)</Button>
  </Page>
  <Page Name="Install">
    <Richedit Name="EulaRichedit" X="11" Y="80" Width="-11" Height="-70" TabStop="yes" FontId="0" HexStyle="0x800000" />
    <Checkbox Name="EulaAcceptCheckbox" X="-11" Y="-41" Width="260" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallAcceptCheckbox)</Checkbox>
    <Button Name="OptionsButton" X="-171" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.InstallOptionsButton)</Button>
    <Button Name="InstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallInstallButton)</Button>
    <Button Name="WelcomeCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
  </Page>
  <Page Name="Options">
    <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.OptionsHeader)</Text>
    <Text X="11" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OptionsLocationLabel)</Text>
    <Editbox Name="FolderEditbox" X="11" Y="143" Width="-91" Height="21" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
    <Button Name="BrowseButton" X="-11" Y="142" Width="75" Height="23" TabStop="yes" FontId="3">#(loc.OptionsBrowseButton)</Button>
    <Button Name="OptionsOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsOkButton)</Button>
    <Button Name="OptionsCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsCancelButton)</Button>
  </Page>
  <Page Name="Progress">
    <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.ProgressHeader)</Text>
    <Text X="11" Y="121" Width="70" Height="17" FontId="3" DisablePrefix="yes">#(loc.ProgressLabel)</Text>
    <Text Name="OverallProgressPackageText" X="85" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OverallProgressPackageText)</Text>
    <Progressbar Name="OverallCalculatedProgressbar" X="11" Y="143" Width="-11" Height="15" />
    <Button Name="ProgressCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ProgressCancelButton)</Button>
  </Page>
  <Page Name="Modify">
    <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.ModifyHeader)</Text>
    <Button Name="RepairButton" X="-171" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.ModifyRepairButton)</Button>
    <Button Name="UninstallButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ModifyUninstallButton)</Button>
    <Button Name="ModifyCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.ModifyCloseButton)</Button>
  </Page>
  <Page Name="Success">
    <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.SuccessHeader)</Text>
    <Button Name="LaunchButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessLaunchButton)</Button>
    <Text Name="SuccessRestartText" X="-11" Y="-51" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.SuccessRestartText)</Text>
    <Button Name="SuccessRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessRestartButton)</Button>
    <Button Name="SuccessCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.SuccessCloseButton)</Button>
  </Page>
  <Page Name="Failure">
    <Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.FailureHeader)</Text>
    <Hypertext Name="FailureLogFileLink" X="11" Y="121" Width="-11" Height="42" FontId="3" TabStop="yes" HideWhenDisabled="yes">#(loc.FailureHyperlinkLogText)</Hypertext>
    <Hypertext Name="FailureMessageText" X="22" Y="163" Width="-11" Height="51" FontId="3" TabStop="yes" HideWhenDisabled="yes" />
    <Text Name="FailureRestartText" X="-11" Y="-51" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRestartText)</Text>
    <Button Name="FailureRestartButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FailureRestartButton)</Button>
    <Button Name="FailureCloseButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FailureCloseButton)</Button>
  </Page>
</Theme>

        可以看到這個XML檔案可以通過自定義的方法來進行介面修改,其中#(loc.Caption)之類的是WiX使用變數的語法,這些變數都是已經內嵌在框架中了,我們還可以通過在bundle.wxs檔案中自行增加變數的方法來達到增加變數的目的。MyLocalize.wxl檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization xmlns="http://schemas.microsoft.com/wix/2006/localization">
  <String Id="Caption">Setup [WixBundleName]</String>
  <String Id="Title">[WixBundleName]</String>
  <String Id="ConfirmCancelMessage">Are you sure you want to cancel?</String>
  <String Id="HelpHeader">Setup Help</String>
  <String Id="HelpText">
    /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or
    creates a complete local copy of the bundle in directory. Install is the default.

    /passive | /quiet -  displays minimal UI with no prompts or displays no UI and
    no prompts. By default UI and all prompts are displayed.

    /norestart   - suppress any attempts to restart. By default UI will prompt before restart.
    /log log.txt - logs to a specific file. By default a log file is created in %TEMP%.
  </String>
  <String Id="HelpCloseButton">&Close</String>
  <String Id="InstallAcceptCheckbox">I &agree to the license terms and conditions</String>
  <String Id="InstallOptionsButton">&Options</String>
  <String Id="InstallInstallButton">&Install</String>
  <String Id="InstallCloseButton">&Close</String>
  <String Id="OptionsHeader">Setup Options</String>
  <String Id="OptionsLocationLabel">Install location (disk root path is not recommanded):</String>
  <String Id="OptionsBrowseButton">&Browse</String>
  <String Id="OptionsOkButton">&OK</String>
  <String Id="OptionsCancelButton">&Cancel</String>
  <String Id="ProgressHeader">Setup Progress</String>
  <String Id="ProgressLabel">Processing:</String>
  <String Id="OverallProgressPackageText">Initializing...</String>
  <String Id="ProgressCancelButton">&Cancel</String>
  <String Id="ModifyHeader">Modify Setup</String>
  <String Id="ModifyRepairButton">&Repair</String>
  <String Id="ModifyUninstallButton">&Uninstall</String>
  <String Id="ModifyCloseButton">&Close</String>
  <String Id="SuccessHeader">Setup Successful</String>
  <String Id="SuccessLaunchButton">&Launch</String>
  <String Id="SuccessRestartText">You must restart your computer before you can use the software.</String>
  <String Id="SuccessRestartButton">&Restart</String>
  <String Id="SuccessCloseButton">&Close</String>
  <String Id="FailureHeader">Setup Failed</String>
  <String Id="FailureHyperlinkLogText">One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>.</String>
  <String Id="FailureRestartText">You must restart your computer to complete the rollback of the software.</String>
  <String Id="FailureRestartButton">&Restart</String>
  <String Id="FailureCloseButton">&Close</String>
</WixLocalization>

        我們只需要修改每個String節點中的內容,就可以達到本地化安裝內容的目的。

自定義安裝序列

        上面我們已經將安裝介面全部自定義完畢,臉面上的事情總算是做完了,我們要解決的根本問題還在眼前,如何自定義調整安裝順序?答案就是通過Chain節點來完成。Chain節點定義了打包安裝的順序,預設是從上到下逐個完成,當然你也可以在子節點中通過After屬性來調整安裝順序。

        首先是判斷並安裝.NET Framework 4.0環境。

      <ExePackage Id="Netfx4Full"
          Cache="no"
          Compressed="no"
          PerMachine="yes"
          Permanent="yes"
          Vital="yes" InstallCommand=" /q /norestart"
          SourceFile="dotNetFx40_Full_x86_x64.exe"
          DownloadUrl="http://go.microsoft.com/fwlink/?LinkId=164193"
          DetectCondition="DotNetFramework40FullInstallRegValue=1" />

        我們知道,從網上下載得到的.NET 4.0是一個exe安裝包,因此我們要用ExePackage進行宣告。

        Compressed表示是否要將該安裝包打包在bootstrapper.exe檔案中,我們回憶一下上一講,上一講我們的Setup1.msi檔案因為沒有帶上該屬性,就自動將安裝包打包在bootstrapper中了,因此我們只得到了一個安裝檔案。如果Compressed="no",那就會在bootstrapper.exe邊上出現另外一個單獨的安裝檔案,這樣可以減小我們釋出的尺寸。

        PerMachine是表示是否要採用UAC許可權進行安裝,因為有些安裝檔案需要以管理員身份執行才能安裝成功,而我們現在採取的是靜默安裝策略,因此需要在定義安裝的時候就表明是否需要UAC許可權。

        Permanent表示解除安裝時是否需要同時解除安裝該安裝包。yes表示解除安裝產品時該安裝包不需要同時解除安裝,將會永遠留在客戶的計算機裡面。

        Vital表示是否該安裝時必須的,如果該安裝是必須的,那如果該安裝沒有成功,後續的所有安裝都不會進行。

        InstallCommand是在安裝時需要跟上的引數,我們知道.NET的靜默安裝需要帶上“ /q /norestart”引數,這樣我們用bootstrapper託管下就可以讓.NET靜默安裝完畢了。

        SourceFile表明安裝檔案的名稱是什麼,它和DownloadUrl是任選的,也就是說如果安裝時發現該檔案包不存在的話,需要從什麼URL進行自動下載並安裝。

        DetectCondition是bootstrapper的精髓,它用一個表示式來判斷一臺計算機上該包是否已經安裝過,DotNetFramework40FullInstallRegValue和文件最後的util:RegistrySearch節點ID是一致的,util:RegistrySearch代表檢視登錄檔中的某個表項的值的變數,然後匹配該登錄檔項值滿足條件,則說明已經安裝,如果不滿足則說明需要安裝。

<MsiPackage Compressed="no" SourceFile="SSCERuntime-CHS.msi" Vital="yes" DisplayInternalUI="no" Permanent="yes" ForcePerMachine="yes"
          DownloadUrl="http://go.microsoft.com/fwlink/?LinkID=166085"
          InstallCondition="VersionNT = v5.1" />
        MsiPackage節點上一講已經使用過,如果要安裝MSI檔案必須用這個節點,它的屬性和ExePackage大同小異,DisplayInternalUI表示是否要靜默安裝,ForcePerMachine代表需要UAC許可權。InstallCondition代表需要安裝的判定條件,請注意,這個和上面的DetectCondition是反的,上面是如果判定為false則安裝,這個是判定為true則安裝。(看到這兒實在覺得這是不是兩個人寫的框架啊,屬性名稱也不統一,連判定安裝條件也要反一反,很是反人類啊!)InstallCondition="VersionNT = v5.1"表示如果是XP則進行安裝,VersionNT是框架自帶的變數,可以在WiX的網站中查詢並得到其他的變數使用方法。
      <MsiPackage Compressed="no" SourceFile="IntSig.CamCard.Installer.msi" Vital="yes" DisplayInternalUI="no" Permanent="no"  ForcePerMachine="yes">
        <MsiProperty Name="TARGETDIR" Value="[InstallFolder]"/>
      </MsiPackage>

        最後一個MSI就是我們自己定義的MSI了,前面是把環境安裝包給安裝掉,最後就是安裝我們自己的產品包。重點需要解釋的是<MsiProperty Name="TARGETDIR" Value="[InstallFolder]"/>節點,這個其實就是相當於我們使用msiexec.exe系統命令進行MSI安裝,如果我們要把MSI靜默安裝,又必須指定要裝到某個目錄下,需要使用msiexec.exe ***.msi TARGETDIR="C:\AA\" 這樣的命令格式,其實上面的MsiProperty屬性就是把之前在bootstrapper啟動介面選項中選中的安裝目錄路徑通過變數傳送了過來。

        完成上面的這些工作,再把所需要的安裝包,配置檔案都放到工程裡面後,經過專案生成,我們就拿到很完美的bootstrapper了。現在的結果是,最極限情況下我們只需要釋出一個bootstrapper,後續所有的軟體依賴就能下載並安裝,極大得減少了使用者下載安裝的負擔。並且因為可以動態判斷依賴包,所以使用者的安裝速度也得到極大的提升。最最重要的是,只需要在安裝第一個包的時候進行一次UAC詢問,後續就不再會有類似的惱人確認對話方塊了。至此,我們之前所有提出的VS打包專案的不足就全部解決了。