65.9K
CodeProject 正在变化。 阅读更多。
Home

为 ASP.NET Web 应用程序创建 WIX 安装程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (72投票s)

2010年10月3日

CPOL

15分钟阅读

viewsIcon

548626

downloadIcon

8766

创建 ASP.NET Web 应用程序的 WIX 安装程序的示例。

intro.jpg

目录

引言

几个月前,我开始为 Web 应用程序编写安装程序。我决定学习如何用 WIX 编写,因为我听说过它的好评。起初并不容易,但我在这个平台上花费的时间对我来说非常有价值。从那时起,我意识到拥有一个安装程序是软件交付过程的重要组成部分。为几个项目编写安装程序后,这项活动变得轻松快捷。在本文中,我将介绍使用 WIX 创建 Web 应用程序安装程序的代码,并向您展示如何通过命令行命令进行编译。我还将介绍一个 msbuild 脚本,用于创建 Web 应用程序的安装程序,从发布 Web 应用程序,到收集应用程序内容,再到编译安装程序。您将理解这个过程,从而能够根据您的 Web 应用程序的需求进行扩展。

为什么我们需要 Web 应用程序的安装程序?

在我职业生涯中,我主要开发 ASP.NET Web 应用程序。不是全部,但大部分都是。软件交付过程总是被推到次要地位,因此几乎被所有团队成员低估。不仅在早期,即使在今天,我也遇到过认为构建安装程序不必要的优秀程序员。Xcopy (robocopy) 总是能很好地工作,并且在生产环境中会临时解决出现的问题。但这不专业,不是吗?好吧,如果您正在为自己或朋友创建一个博客页面,并将其部署到托管服务,那么您可能不需要安装程序。由于缺乏权限,您可能无法在托管服务上执行安装程序。您唯一能获得的是 FTP 访问权限。那么,我们为什么要为 Web 应用程序编写安装程序呢?我会考虑为 Web 应用程序编写安装程序,该应用程序应该被安装多次,并且由 Web 应用程序作者以外的人安装。如果开发过程中包含持续集成或持续交付流程,我也会考虑编写 Web 应用程序安装程序。为了说清楚,请看下面的两张图。

b01.jpg

图 B01

图 B01 展示了一个在大型公司工作的人应该熟悉的例子。开发人员创建软件,管理员负责服务器。他们互不认识,公司人员流动性很高。在这种情况下,管理员从开发人员那里获得安装程序,并在不与开发人员协调的情况下安装软件。如果安装程序编写得非常好,那么管理员所需的文档最少。如果一年后服务器发生故障,管理员将能够轻松地重新安装应用程序,而不必联系可能因为获得更好的机会而在其他地方工作的开发人员。另一个安装程序是绝佳选择的例子是进行持续集成时。在这种情况下,您通常希望在夜间与夜间构建并行地重新部署您的应用程序。图 B02 显示了这种情况。

b02.jpg

图 B02

文章开头的理论部分已经完成,让我们来看一些代码。

创建 Web 应用程序

安装程序需要一些输入。为此,我们需要创建一个简单的 Web 应用程序。当然,我已经创建了一个。您可以在源代码的 *MyWeb* 目录中找到它。它非常简单,只有一个网页。尽管它很简单,但我还是将其放入 Visual Studio 的解决方案文件中,以便可以轻松地通过自定义库进行扩展。创建安装程序的代码正在等待一个包含 Web 应用程序的解决方案作为输入。

创建 WIX 代码

在本节中,我将介绍使用 WIX 创建安装程序的代码。我使用的是 WIX 3.0 版本。我在几个项目中发现这段代码非常通用。我的意思是,我总是从这个模板开始。和许多事物一样,没有什么是完美的,所以我从一个项目到另一个项目修改这个模板,使其越来越好。我不认为当前版本是最好的,但它对我来说效果很好。如果我修改了本文中的某些部分使其写得更好,我将在将来修改代码。 Web 应用程序的安装程序与其他安装程序一样,不能普遍适用于所有 Web 应用程序。尽管如此,请将其作为一个可以根据您的 Web 应用程序需求进行扩展的示例。我不想重复其他写得很好的文章中已经写过的内容,因此我将不会深入探讨 WIX 代码的细节。我期望读者了解使用 WIX 编写安装程序的基础知识。 WIX 的一个很好的资源是官方教程 [1]。源代码包含注释以便更好地定位。我现在将介绍的模板分为五个文件,如图 C01 所示。

c01.jpg

图 C01 - 安装程序源代码文件块图。
文件名 描述
ConfigurationInitialize 声明安装程序中使用的变量。
产品 主文件。定义目标目录结构、主 UI、功能等。
IISConfiguration 执行 IIS 中要求的更改。
UIDialogs 自定义 UI 屏幕的定义。
MyWebUI UI 屏幕顺序的定义。
WebSiteContent 生成代码。定义 Web 应用程序输出中的文件。

在描述代码之前,我应该说明安装程序具体做什么。其可视化表示可以在图 C02 中看到。

C02.jpg

图 C02 - 安装程序执行的步骤

从图 C02 可以看出,安装程序执行几乎所有 Web 应用程序都需要执行的步骤。很明显,您需要将您的应用程序复制到目标计算机上的某个目录。您当然需要设置 IIS 以便 Web 应用程序可供客户端访问。特别是在部署到 IIS 6 时,需要注册 Web 站点以在 ASP.NET 2 下运行的步骤,最后更改连接字符串是我在我写过的每个 Web 应用程序中都需要的特性。描述的操作需要从处理安装的人那里收集输入信息。因此,安装程序的一部分是一组 UI 屏幕,在安装程序开始处理安装之前需要填写这些屏幕。

注意:您可能已经注意到,安装程序会创建一个应用程序池。对于此操作,安装程序会询问应用程序池将以哪个帐户运行。请阅读 [6] 以了解该帐户需要什么。我花了些时间才弄清楚为什么安装程序创建的应用程序池在特定服务器上无法启动。

ConfigurationInitialize.wxi - 变量定义

此文件是一个包含文件,因此结构与其他文件略有不同。

<?xml version="1.0" encoding="utf-8"?>
<Include>
    <!-- +++++++++++++++++++ VIRTUAL_DIR_VAL property initialize +++++++++++++++++++ -->
    <Property Id="VIRTUAL_DIR_VAL" Value="MyWeb" />

    <!-- +++++++++++++++++++ web app name properties initialize ++++++++++++++++++++ -->
    <Property Id="WEB_APP_NAME" Value="MyWeb" />

    <!-- +++++++++++++++++++ app pool identity properties initialize +++++++++++++++ -->
    <Property Id="WEB_APP_POOL_IDENTITY_DOMAIN" Value="POOL_DOMAIN" />
    <Property Id="WEB_APP_POOL_IDENTITY_NAME" Value="account-name" />
    <Property Id="WEB_APP_POOL_IDENTITY_PWD" Hidden="yes" />
    
    <!-- +++++++++++++++++++++++++++ Connection String +++++++++++++++++++++++++++ -->
    <Property Id="CONNECTION_STRING" 
      Value="Data Source=|SERVER|;Initial Catalog=|Database name|;
             User Id=|LOGIN|;Password=|PASSWORD|;Persist Security Info=True" />
</Include>
列表 C01 - ConfigurationInitialize.wxi

从上面的列表可以看出,该文件仅包含带有默认值的 ID/VALUE 变量集合。启动安装时,可以直接从命令行自定义这些值。拥有这些变量的主要原因是为了映射到 UI 对话框并用于安装的参数化。例如,在安装过程中会提示用户指定连接字符串,并最终将其写入配置文件。

Product.wxs - 安装程序的主代码

此代码是安装程序的主代码。可以在列表 C02 中看到。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
     xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
     
    <Product Id="{FA1B9338-B6F6-413e-B67F-86CA8BCED6E8}" 
             Name="MyWeb.Setup" 
             Language="1033" 
             Version="1.0.0.0" 
             Manufacturer="MyWeb.Setup" 
             UpgradeCode="{E5C9F391-5787-4fd1-81E6-D1A4A91D226D}">
        
        <Package InstallerVersion="200" Compressed="yes" />

        <Media Id="1" Cabinet="MyWeb.cab" EmbedCab="yes" />

        <!-- 
            * Variables 
         -->
        <!-- Configurable install location -->
        <PropertyRef Id="NETFRAMEWORK30_SP_LEVEL" />
        <Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />

        <!-- Creating directories -->
        <Directory Id="TARGETDIR" Name="SourceDir">
            <!-- Install stuff into program files folder. -->
            <Directory Id="ProgramFilesFolder">
                <!-- In program files create folder with name MyWeb. -->
                <Directory Id="INSTALLLOCATION" Name="MyWeb">
                    <!-- This is the folder where the website content will be located --> 
                    <Directory Id="MYWEBWEBSITE" Name="Website">
                        <!-- Continue in DirectoryRef with specific name -->
                    </Directory>
                    <!-- Here you can add another directories -->
                </Directory>
            </Directory>
        </Directory>

        <!-- Complete feature which will be installed. -->
        <Feature Id="Complete"
             Title="MyWeb - My awesome web"
             Level="1"
             Display="expand"
             ConfigurableDirectory="INSTALLLOCATION">
             
             <!-- Main content of the Complete feature. -->
            <Feature Id="MainContent"
                     Title="MyWeb Website"
                     Description="The website content"
                     Level="1">
                
                <!-- Include IIS Configuration. -->
                <ComponentGroupRef Id="MyWebIssConfiguration" />
                
                <!-- Include web content. -->
                <ComponentGroupRef Id="MyWebWebComponents" />
                
                <!-- Perform changes in the web.config file. -->
                <ComponentRef Id="WebConfigCmp" />

            </Feature>
        </Feature>

        <DirectoryRef Id="MYWEBWEBSITE">
            <!-- Component handling the web.config -->
            <Component Id="WebConfigCmp" Guid="">
                <!-- Copy web.config to MYWEBWEBSITE folder. -->
                <File Id="WebConfigFile" KeyPath="yes" 
                  Source="$(var.publishDir)\Web.config" Vital="yes" />
                <util:XmlFile Id="ModifyConnectionString"
                         Action="setValue"
                         Permanent="yes"
                         ElementPath="/configuration/connectionStrings/
                                      add[\[]@name='MyConnectionString'[\]]"
                         Name="connectionString"
                         File="[#WebConfigFile]"
                         Value="[CONNECTION_STRING]"
                         SelectionLanguage="XSLPattern"
                         Sequence="1" />
            </Component>
        </DirectoryRef>

        <!-- .NET Framework 3.0 SP 1 must be installed -->
        <Property Id="FRAMEWORKBASEPATH">
            <RegistrySearch Id="FindFrameworkDir" Root="HKLM" 
              Key="SOFTWARE\Microsoft\.NETFramework" 
              Name="InstallRoot" Type="raw"/>
        </Property>

        <Property Id="ASPNETREGIIS" >
            <DirectorySearch Path="[FRAMEWORKBASEPATH]" 
                        Depth="4" Id="FindAspNetRegIis">
                <FileSearch Name="aspnet_regiis.exe" MinVersion="2.0.5"/>
            </DirectorySearch>
        </Property>
        
        <!-- Switch ASP.NET to version 2.0 -->
        <CustomAction Id="MakeWepApp20" Directory="MYWEBWEBSITE" 
          ExeCommand="[ASPNETREGIIS] -norestart -s W3SVC/1/ROOT/[WEB_APP_NAME]" 
          Return="check"/>

        <InstallExecuteSequence>
            <Custom Action="MakeWepApp20" After="InstallFinalize">
                   ASPNETREGIIS AND NOT Installed</Custom>
        </InstallExecuteSequence>
        
        <!-- License and images -->
        <WixVariable Id="WixUILicenseRtf" Value="$(var.MyWebResourceDir)\License.rtf" />

        <!-- Specify UI -->
        <UIRef Id="MyWebUI" />
        
    </Product>
</Wix>
列表 C02 - Product.wxs

为了更好地理解,列表 C02 包含注释,代码并不难理解。有点棘手的是在配置文件复制到所需位置后更改连接字符串。这可以在列表 C03 中看到。

<util:XmlFile Id="ModifyConnectionString"
     Action="setValue"
     Permanent="yes"
     ElementPath="/configuration/connectionStrings/
                  add[\[]@name='MyConnectionString'[\]]"
     Name="connectionString"
     File="[#WebConfigFile]"
     Value="[CONNECTION_STRING]"
     SelectionLanguage="XSLPattern"
     Sequence="1" />
列表 C03 - 自定义连接字符串

因为这不应被视为基础知识,所以我将您指向解释此代码的优秀文章 [2]。

IISConfiguration.wxs - IIS 配置

从文件名可以看出,此代码将负责 IIS 配置。这意味着创建虚拟目录和 Web 应用程序池。代码可以在列表 C04 中看到。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    
    <Fragment>
        <?include ConfigurationInitialize.wxi ?>

        <!-- Install to default web site -->
        <iis:WebSite Id="DefaultWebSite" Description='Default Web Site'>
            <iis:WebAddress Id="AllUnassigned" Port="80" />
        </iis:WebSite>

        <DirectoryRef Id="MYWEBWEBSITE">
            <!-- Configuring app pool -->
            <Component Id="MyWebAppPoolCmp" Guid="" KeyPath="yes">
                <util:User Id="MyWebAppPoolUser"
                           CreateUser="no"
                           Name="[WEB_APP_POOL_IDENTITY_NAME]"
                           Password="[WEB_APP_POOL_IDENTITY_PWD]"
                           Domain="[WEB_APP_POOL_IDENTITY_DOMAIN]" />
                <iis:WebAppPool Id="MyWebAppPool"
                                Name="[WEB_APP_NAME]"
                                Identity="other"
                                User="MyWebAppPoolUser" />
            </Component>
        
            <!-- Configure virtual dir -->
            <Component Id="MyWebVirtualDirCmp" 
                   Guid="{751DEB01-ECC1-48ff-869A-65BCEE9E0528}" 
                   KeyPath="yes" >
                <iis:WebVirtualDir Id="MyWebVirtualDir" 
                          Alias="[VIRTUAL_DIR_VAL]" Directory="MYWEBWEBSITE" 
                          WebSite="DefaultWebSite">
                    <iis:WebDirProperties Id="MyWebVirtDirProperties" 
                       AnonymousAccess="no" BasicAuthentication="no" 
                       WindowsAuthentication="yes" />
                    <iis:WebApplication Id="MyWebWebApplication" 
                       Name="[VIRTUAL_DIR_VAL]" />
                </iis:WebVirtualDir>
            </Component>
        </DirectoryRef>

        <ComponentGroup Id="MyWebIssConfiguration">
            <ComponentRef Id="MyWebVirtualDirCmp" />
            <ComponentRef Id="MyWebAppPoolCmp" />
        </ComponentGroup>
        
    </Fragment>
</Wix>
列表 C04 - IIS 配置

您可能已经注意到,该模板使用 *WIX 3.0 IIS 扩展* 来设置 IIS。这完全兼容 IIS 5 和 IIS 6。幸运的是,它也适用于 IIS 7,但您必须在目标计算机上安装 *IIS 兼容性管理包*。但是,当您尝试使用 IIS 6 中不存在的某些 IIS 7 功能时,可能会出现另一个问题。例如,考虑一个在*集成*模式下运行的应用程序池。在这种情况下,您需要运行自定义操作来调用 *AppCmd.exe* 以进行更改,因为应用程序池的默认模式是*经典*模式。有关更多信息,请参见 [7]。

UIDialogs.wxs - 自定义 UI 屏幕

如前所述,安装程序会收集用户信息。为此,我们需要添加一些带有需要填写的文本框的自定义屏幕。如列表 C05 所示,代码有点冗长。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Fragment>
        <UI>
            <Dialog Id="IisSetupDlg" Width="370" Height="270" 
                     Title="IIS Settings - [ProductName]" NoMinimize="yes">
                <!-- Virtual Dir prompt -->
                <Control Id="VirtualDirLabel" Type="Text" X="45" Y="73" 
                   Width="100" Height="15" TabSkip="no" Text="&Virtual Directory:" />
                <Control Id="VirtualDirEdit" Type="Edit" X="45" 
                   Y="85" Width="220" Height="18" Property="VIRTUAL_DIR_VAL" Text="{80}" />
                <!-- Back button -->
                <Control Id="Back" Type="PushButton" X="180" Y="243" 
                        Width="56" Height="17" Text="&Back">
                    <Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
                </Control>
                <Control Id="Next" Type="PushButton" X="236" Y="243" 
                   Width="56" Height="17" Default="yes" Text="&Next">
                    <Publish Event="NewDialog" Value="PoolSettingsDlg">
                        <!--if settings are correct, allow next dialog-->
                        <![CDATA[VIRTUAL_DIR_VAL <> ""]]>
                    </Publish>
                </Control>
                <Control Id="Cancel" Type="PushButton" X="304" Y="243" 
                           Width="56" Height="17" Cancel="yes" Text="Cancel">
                    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
                </Control>
                <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" 
                     Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
                <Control Id="Description" Type="Text" X="25" Y="23" 
                       Width="280" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>Please enter IIS Configuration</Text>
                </Control>
                <Control Id="BottomLine" Type="Line" X="0" Y="234" 
                      Width="370" Height="0" />
                <Control Id="Title" Type="Text" X="15" Y="6" 
                        Width="200" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>{\WixUI_Font_Title}IIS Settings</Text>
                </Control>
                <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
            </Dialog>
            
            <Dialog Id="PoolSettingsDlg" Width="370" Height="270" 
                     Title="Application Pool Settings - [ProductName]" NoMinimize="yes">
                <!-- name of the application pool -->
                <Control Id="PoolNameLabel" Type="Text" X="45" Y="73" 
                    Width="100" Height="15" TabSkip="no" Text="&Pool name:" />
                <Control Id="PoolNameEdit" Type="Edit" X="45" Y="85" 
                    Width="220" Height="18" Property="WEB_APP_NAME" Text="{80}" />
                <!-- domain -->
                <Control Id="DomainPoolLabel" Type="Text" X="45" Y="105" 
                    Width="100" Height="15" TabSkip="no" Text="&Domain for AppPool:" />
                <Control Id="DomainPoolEdit" Type="Edit" X="45" Y="117" 
                    Width="220" Height="18" 
                    Property="WEB_APP_POOL_IDENTITY_DOMAIN" Text="{80}" />
                <!-- Login -->
                <Control Id="LoginPoolLabel" Type="Text" X="45" Y="137" 
                  Width="100" Height="15" TabSkip="no" Text="&Login for AppPool:" />
                <Control Id="LoginPoolEdit" Type="Edit" X="45" Y="149" 
                  Width="220" Height="18" Property="WEB_APP_POOL_IDENTITY_NAME" Text="{80}" />
                <!-- Password -->
                <Control Id="PasswordPoolLabel" Type="Text" X="45" Y="169" 
                  Width="100" Height="15" TabSkip="no" Text="&Password for AppPool:" />
                <Control Id="PasswordPoolEdit" Type="Edit" X="45" Y="181" 
                  Width="220" Height="18" Property="WEB_APP_POOL_IDENTITY_PWD" 
                  Text="{80}" Password="yes" />
                <!-- Back button -->
                <Control Id="Back" Type="PushButton" X="180" Y="243" 
                  Width="56" Height="17" Text="&Back">
                    <Publish Event="NewDialog" Value="IisSetupDlg">1</Publish>
                </Control>
                <Control Id="Next" Type="PushButton" X="236" Y="243" 
                  Width="56" Height="17" Default="yes" Text="&Next">
                    <Publish Event="NewDialog" Value="ConnectionStringDlg">
                        <!--if settings are correct, allow next dialog-->
                        <![CDATA[WEB_APP_NAME <> "" or WEB_APP_POOL_IDENTITY_DOMAIN <> 
                          "" or WEB_APP_POOL_IDENTITY_NAME <> "" 
                          or WEB_APP_POOL_IDENTITY_PWD <> ""]]>
                    </Publish>
                </Control>
                <Control Id="Cancel" Type="PushButton" X="304" Y="243" 
                        Width="56" Height="17" Cancel="yes" Text="Cancel">
                    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
                </Control>
                <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" 
                  Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
                <Control Id="Description" Type="Text" X="25" Y="23" 
                       Width="280" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>Please enter AppPool Configuration for IIS</Text>
                </Control>
                <Control Id="BottomLine" Type="Line" X="0" 
                         Y="234" Width="370" Height="0" />
                <Control Id="Title" Type="Text" X="15" Y="6" 
                         Width="200" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>{\WixUI_Font_Title}Application Pool Settings</Text>
                </Control>
                <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
            </Dialog>

            <Dialog Id="ConnectionStringDlg" Width="370" 
                   Height="270" Title="Database Settings - [ProductName]" NoMinimize="yes">
                <!-- Connection String -->
                <Control Id="ConnectionStringLabel" Type="Text" X="45" Y="73" 
                  Width="100" Height="15" TabSkip="no" Text="&Connection String:" />
                <Control Id="ConnectionStringEdit" Type="Edit" X="45" Y="95" 
                  Width="220" Height="18" Property="CONNECTION_STRING" Text="{200}" />
                <!-- Back button -->
                <Control Id="Back" Type="PushButton" X="180" Y="243" 
                         Width="56" Height="17" Text="&Back">
                    <Publish Event="NewDialog" Value="PoolSettingsDlg">1</Publish>
                </Control>
                <Control Id="Next" Type="PushButton" X="236" Y="243" 
                       Width="56" Height="17" Default="yes" Text="&Next">
                    <Publish Event="NewDialog" Value="CustomizeDlg">
                        <!--if settings are correct, allow next dialog-->
                        <![CDATA[CONNECTION_STRING <> ""]]>
                    </Publish>
                </Control>
                <Control Id="Cancel" Type="PushButton" X="304" Y="243" 
                  Width="56" Height="17" Cancel="yes" Text="Cancel">
                    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
                </Control>
                <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" 
                  Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
                <Control Id="Description" Type="Text" X="25" Y="23" 
                       Width="280" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>Please enter database configuration</Text>
                </Control>
                <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
                <Control Id="Title" Type="Text" X="15" Y="6" 
                        Width="200" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>{\WixUI_Font_Title}Database Settings</Text>
                </Control>
                <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
            </Dialog>
            
        </UI>
    </Fragment>
</Wix>
列表 C05 - 自定义 UI 屏幕

基本上,三个屏幕包含标签、文本框、按钮及其位置的定义。

MyWebUI.wxs

此文件指定屏幕流程。代码可以在列表 C06 中看到。

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Fragment>
        <!-- MyWeb UI -->
        <UI Id="MyWebUI">

            <UIRef Id="WixUI_FeatureTree" />
            <UIRef Id="WixUI_ErrorProgressText" />

            <DialogRef Id="IisSetupDlg" />
            <!-- Injection of custom UI. -->
            <Publish Dialog="LicenseAgreementDlg" Control="Next" 
                 Event="NewDialog" Value="IisSetupDlg" 
                 Order="3">LicenseAccepted = "1"</Publish>
            <Publish Dialog="CustomizeDlg" Control="Back" 
                 Event="NewDialog" Value="ConnectionStringDlg">1</Publish>
        </UI>
    </Fragment>
</Wix>
列表 C06 - UI 流程规范

WebSiteContent.wxs

最后,我们来到最后一个文件,即 Web 应用程序的生成内容。此文件指定了哪些页面 (ASPX)、CSS 文件、JavaScript 代码、DLL 等将包含在安装程序中,从而在安装过程中复制到所需位置。该文件是如何生成的将在下一节中详细描述。

构建 MSI 软件包的方法

在前一节中,我们讨论了一些编写安装软件包的代码,现在是时候编译它了。在本章中,我将介绍构建 MSI 软件包的命令。我习惯于通过命令行编译 MSI 软件包,尽管有一个 Visual Studio 插件。当然,当我开始使用 WIX 时,我使用了这个 Visual Studio 插件,但随着时间的推移,我遇到了插件的问题。通过解决插件在不同环境中无法工作的原因等问题,我深入研究了插件实际做什么的细节,并意识到它几乎什么都没做。所以我决定直接使用命令行,再也没有回到插件。

创建 MSI 软件包的过程的可视化解释在图 D1 中描述。基本上,创建 MSI 软件包需要实现四个主要步骤。

d1.jpg

图 D1 - 创建 MSI 软件包

第一步是以 Release 模式构建解决方案,以确保所有 DLL 都已更新。此步骤之后是发布 Web 应用程序。这是每个人都知道的 Visual Studio 中的发布步骤(右键单击项目 -> 发布)。接下来是非常重要的一步,即收集 Web 应用程序。基本上,收集上一步的输出。最后是编译我们在第二章中创建的 WIX 代码。

为什么在收集 Web 应用程序内容时会出现感叹号?

在 WIX 中,建议在开始时一次性收集文件。之后,应手动维护生成的内容。请参见图 D2 中的流程。

d2.jpg

图 D2 - 推荐的安装程序构建流程

然而,这种方法对于计划在项目中持续集成的人来说并不方便。手动维护 Web 应用程序的输出总是非常困难。Web 应用程序的情况很困难,因为在添加文件(ASPX、CSS、HTML)时,您必须手动更新 WIX 脚本。在持续集成中,您期望新的文件,因此新的 Web 应用程序发布将由构建安装包的过程自动触发,并且您的新内容将添加到安装程序中。为了实现此目标,您必须在每次触发新的项目构建时运行 Web 应用程序输出的收集。但正如我所提到的,这并不推荐。

如果在每次构建中都运行收集会发生什么?您可以(但不必)收到与 WIX 组件相关的奇怪警告。我将在本章中介绍我在本章开头描述的过程的命令。我打破了图 D2 中的规则,并将继续将文件收集作为构建的一部分。

让我们在列表 D1 中看看整个批处理脚本。

REM Setting variables...
REM Name of the folder where the 'publish' from msbuild target will be performed
set publishFolder=publish\
REM Remove complete publish folder in order to be sure that evrything will be newly compiled
rmdir /S /Q %publishFolder%
REM Rebuild entire solution
msbuild /p:Configuration=Release /t:ReBuild ..\MyWeb\MyWeb.sln
REM Publish your web site
msbuild /t:ResolveReferences;_CopyWebApplication 
  /p:Configuration=Release;OutDir=..\..\Setup\%publishFolder%\bin\;
  WebProjectOutputDir=..\..\Setup\%publishFolder% ..\MyWeb\MyWeb\MyWeb.csproj
REM Delete debug and setup web.configs to prevent 'heat' from harveting it
del %publishFolder%web_setup.config
del %publishFolder%web.config
REM Harvest all content of published result
heat dir publish\ -dr MYWEBWEBSITE -ke -srd -cg MyWebWebComponents 
  -var var.publishDir -gg -out WebSiteContent.wxs
REM After the files are beeing harvested, copy the setup web.config 
  as regular web.config back to publish location
copy /Y ..\MyWeb\MyWeb\Web_setup.config %publishFolder%Web.config
REM At last create an installer
candle -ext WixIISExtension -ext WixUtilExtension -ext WiXNetFxExtension 
  -dpublishDir=%publishFolder% -dMyWebResourceDir=. IisConfiguration.wxs 
  Product.wxs WebSiteContent.wxs UiDialogs.wxs MyWebUI.wxs
light -ext WixUIExtension -ext WixIISExtension -ext WixUtilExtension 
  -ext WiXNetFxExtension -out bin\Release\MyWeb.Setup.msi Product.wixobj 
  WebSiteContent.wixobj UiDialogs.wixobj MyWebUI.wixobj IisConfiguration.wixobj
列表 D1 - 用于构建安装程序的批处理脚本

脚本并不难,但包含有趣的内容。前两个命令只是设置变量并清理上一次构建。有关具体描述,请阅读脚本中的注释。列表 D2 中看到的第一个命令是构建整个解决方案,以确保所有程序集和链接的项目都已更新。它还确保在 Release 模式下一切都已更新。

msbuild /p:Configuration=Release /t:ReBuild ..\MyWeb\MyWeb.sln
列表 D2 - 以 Release 模式构建解决方案

列表 D3 中看到的下一个命令是调用 msbuild 工具,但针对您的 Web 应用程序和特定的目标。

msbuild /t:ResolveReferences;_CopyWebApplication 
  /p:Configuration=Release;OutDir=..\..\Setup\%publishFolder%\bin\;
  WebProjectOutputDir=..\..\Setup\%publishFolder% ..\MyWeb\MyWeb\MyWeb.csproj
列表 D3 - 发布 Web 应用程序

命令的结果与在 Visual Studio 中单击“发布”按钮相同。基本上,该命令将复制需要部署到目标计算机的文件。

列表 D4 中看到的接下来的两个命令是删除应用程序输出中的两个配置文件。当然,您会注意到 Web 应用程序有两个配置文件:一个用于本地运行,一个用于部署。

del %publishFolder%web_setup.config
del %publishFolder%web.config
列表 D4 - 从输出中删除配置文件

在收集之前必须删除两者,因为它们都不是要收集的文件。这是因为 web.config 文件在 *Product.wxs* 中被注册为安装程序的一部分。因此,它不能被收集。

既然 Web 应用程序的输出已准备好进行收集,我们可以执行 heat 工具来完成这项工作。请参见列表 D5。

heat dir %publishFolder% -dr MYWEBWEBSITE -ke -srd -cg 
    MyWebWebComponents -var var.publishDir -gg -out WebSiteContent.wxs
列表 D5 - 收集 Web 应用程序输出

文件收集完成后,我们可以将 Web 配置文件复制回发布文件夹,但请注意,设置配置文件作为常规 ASP.NET Web 配置文件复制。因此,配置文件的最终名称为 *web.config*。

copy /Y ..\MyWeb\MyWeb\Web_setup.config %publishFolder%Web.config
列表 D6 - 复制 Web 配置文件

最后,一切都准备好编译 WIX 代码。请参见列表 D7 中的命令。

candle -ext WixIISExtension -ext WixUtilExtension -ext WiXNetFxExtension 
  -dpublishDir=%publishFolder% -dMyWebResourceDir=. IisConfiguration.wxs 
  Product.wxs WebSiteContent.wxs UiDialogs.wxs MyWebUI.wxs
light -ext WixUIExtension -ext WixIISExtension -ext WixUtilExtension -ext WiXNetFxExtension 
  -out bin\Release\MyWeb.Setup.msi Product.wixobj WebSiteContent.wixobj 
  UiDialogs.wixobj MyWebUI.wixobj IisConfiguration.wixobj
列表 D7 - 编译安装程序

从批处理文件到 msbuild

将整个安装程序构建保留在批处理文件中可能相当奇怪和不专业。此外,在持续集成中,我们希望在构建过程中添加更多功能,而这些功能在批处理文件中可能很难添加(但并非不可能)。前一章之所以存在,就是为了理解这一点。现在您已经理解了构建过程,我们可以做得更专业一些,并将其转移到 msbuild 脚本中。在本节中,我将把上一章的批处理脚本重写为 msbuild 脚本。我期望读者了解 msbuild 的基础知识以及如何在其中编写脚本。这真的非常简单,但如果您还没有尝试过,我将为您提供 [3] 的在线资源或 [4] 的一本非常好的书。脚本可以在列表 E01 中看到。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" 
       xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <WebSiteSource>..\MyWeb\MyWeb\</WebSiteSource>
        <ReleaseWebConfig>Web_setup.config</ReleaseWebConfig>
        <PublishF>publish\</PublishF>
        <Publish>..\..\Setup\$(PublishF)</Publish>
        <MsiOut>bin\Release\MyWeb.Setup.msi</MsiOut>
        <WebSiteContentCode>WebSiteContent.wxs</WebSiteContentCode>
        <WebSiteContentObject>WebSiteContent.wixobj</WebSiteContentObject>
        <WixPath></WixPath>
    </PropertyGroup>
    
    <!-- Defining group of temporary files which is the content of the web site. -->
    <ItemGroup>
        <WebSiteContent Include="$(WebSiteContentCode)" />
        <WebSiteContent Include="$(WebSiteContentFile)" />
    </ItemGroup>
    
    <ItemGroup>
        <!-- The group of web configs -->
        <WebConfigs Include="$(PublishF)Web.config" />
        <WebConfigs Include="$(PublishF)$(ReleaseWebConfig)" />
    </ItemGroup>

    <!-- The list of WIX input files -->
    <ItemGroup>
        <WixCode Include="IisConfiguration.wxs" />
        <WixCode Include="Product.wxs" />
        <WixCode Include="$(WebSiteContentCode)" />
        <WixCode Include="UiDialogs.wxs" />
        <WixCode Include="MyWebUI.wxs" />
    </ItemGroup>
    
    <!-- The list of WIX after candle files -->
    <ItemGroup>
        <WixObject Include="IisConfiguration.wixobj" />
        <WixObject Include="Product.wixobj" />
        <WixObject Include="$(WebSiteContentObject)" />
        <WixObject Include="UiDialogs.wixobj" />
        <WixObject Include="MyWebUI.wixobj" />
    </ItemGroup>
    
    <!-- Define default target with name 'Build' -->
    <Target Name="Build">
        <!-- Compile whole solution in release mode -->
        <MSBuild
            Projects="..\MyWeb\MyWeb.sln"
            Targets="ReBuild"
            Properties="Configuration=Release" />
    </Target>

    <!-- Define creating installer in another target -->
    <Target Name="CreateInstaller">
        <!-- Remove complete publish folder in order to 
             be sure that evrything will be newly compiled -->
        <RemoveDir Directories="$(PublishF)" ContinueOnError="false" />
        <MSBuild
            Projects="..\MyWeb\MyWeb\MyWeb.csproj"
            Targets="ResolveReferences;_CopyWebApplication"
            Properties="OutDir=$(Publish)bin\;WebProjectOutputDir=
                        $(Publish);Configuration=Release" />
        
        <!-- Delete debug and setup web.configs to prevent 'heat' from harveting it -->
        <!-- Delete all wix object which remain from previous build -->
        <!-- Delete all other temporary files -->
        <Delete Files="@(WebConfigs)" />
        <Delete Files="@(WixObject)" />
        <Delete Files="@(WebSiteContent)" />

        <!-- Harvest all content of published result -->
        <Exec
            Command='"$(WixPath)heat" dir $(PublishF) -dr MYWEBWEBSITE 
                     -ke -srd -cg MyWebWebComponents -var var.publishDir 
                     -gg -out $(WebSiteContentCode)'
            ContinueOnError="false"
            WorkingDirectory="." />

        <!-- After the files are beeing harvested, copy the setup web.config 
             as regular web.config back to publish location -->
        <Copy
            SourceFiles="$(WebSiteSource)$(ReleaseWebConfig)"
            DestinationFiles="$(PublishF)Web.config"
            ContinueOnError="false" />

        <!-- At last create an installer -->
        <Exec
            Command='"$(WixPath)candle" -ext WixIISExtension -ext WixUtilExtension 
                     -ext WiXNetFxExtension -dpublishDir=$(PublishF) 
                     -dMyWebResourceDir=. @(WixCode, &apos; &apos;)'
            ContinueOnError="false"
            WorkingDirectory="." />
        <Exec
            Command='"$(WixPath)light" -ext WixUIExtension -ext WixIISExtension 
                     -ext WixUtilExtension -ext WiXNetFxExtension -out 
                     $(MsiOut) @(WixObject, &apos; &apos;)'
            ContinueOnError="false"
            WorkingDirectory="." />
        
        <!-- A message at the end -->
        <Message Text="Install package has been created." />
    </Target>
    
    <!-- Optional target for deleting temporary files. Usually after build -->
    <Target Name="DeleteTmpFiles">
        <RemoveDir Directories="$(PublishF)" ContinueOnError="false" />
        <Delete Files="@(WebConfigs);@(WixObject);@(WebSiteContent)" />
    </Target>

</Project>
列表 E01 - 用于创建安装程序的 msbuild 脚本

您可能注意到脚本提供了三个目标。用于重建解决方案、创建安装程序和删除临时文件。可以通过列表 E02 中的命令执行所有目标。

msbuild /t:Build;CreateInstaller;DeleteTmpFiles build_setup.build
列表 E02 - 执行带有所有目标的构建脚本。

结论

本文总结了以自动化方式创建安装程序所需的步骤。最后还有什么要说的?只需享受使用安装包部署您的 Web 应用程序吧!:)

请随时发表评论和/或投票。感谢阅读。

参考文献

  1. http://www.tramontana.co.hu/wix/
  2. http://blogs.technet.com/b/alexshev/archive/2009/05/27/from-msi-to-wix-part-25-installable-items-updating-xml-files-using-xmlfile.aspx
  3. http://msdn.microsoft.com/en-us/library/wea2sca5%28v=VS.90%29.aspx
  4. ISBN-10: 0735626286, ISBN-13: 978-0735626287, Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build (PRO-Developer), Authors: Sayed Ibrahim Hashimi and William Bartholomew
  5. ISBN-10: 0321601912, ISBN-13: 978-0321601919, Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation, Authors: Jez Humble, David Farley
  6. http://beyondthispoint.blogspot.com/2006/04/setting-up-iis6-application-pool.html
  7. http://forums.iis.net/p/1155735/1895551.aspx

历史

  • 2010 年 10 月 3 日
    • 首次发布。
  • 2010 年 11 月 12 日
    • 添加了 WIX 版本。
    • 在*IISConfiguration.wxs - IIS 配置*部分说明了 IIS 7 的兼容性。
© . All rights reserved.