WiX技巧和提示

264

我们已经使用WiX有一段时间了,尽管通常对于使用方便的抱怨很多,但情况还算不错。我正在寻找有关以下内容的有用建议:

  • 设置一个WiX项目(布局、引用、文件模式)
  • 将WiX集成到解决方案中,并构建/发布过程
  • 为新安装和升级配置安装程序
  • 任何你想分享的好的WiX技巧

请查看gui4wix.codeplex.com。 - TarunG
10
标记为“不具建设性”?我从这个问题中学到了很多! 如果 StackOverflow 能够有一点点一致性就更好了,比如:https://dev59.com/jnRB5IYBdhLWcg3wr48V - si618
15
它获得了“203个赞”,这足以证明它的实用性。 - TarunG
SO的问题必须有明确、正确的答案;开放式问题会使人们提出的关于实际问题的问题从首页消失。@Si.:据我所知,这一政策一直存在,但现在执行得更好了;那个问题已经快三年了。 - Jim Dagg
Jim,说得对。这是一个开放性问题,我想这取决于SO社区的决定,但我必须说,将其关闭为“不具建设性”似乎有些奇怪,因为我和许多其他人发现这个问题很有用(例如http://goo.gl/Zqp2X),而且它非常符合FAQ中“基于实际问题的实用、可回答的问题”的部分。 - si618
这很有用(这就是我在这里的原因),但它不是一个问答,而是一个讨论。问题本身似乎是一个合法的问题,目前得到最多赞的答案似乎也是一个合法的答案,但其余的答案只是在添油加醋,这让它感觉像是一次讨论。 - Fls'Zen
31个回答

157
  1. Keep variables in a separate wxi include file. Enables re-use, variables are faster to find and (if needed) allows for easier manipulation by an external tool.

  2. Define Platform variables for x86 and x64 builds

    <!-- Product name as you want it to appear in Add/Remove Programs-->
    <?if $(var.Platform) = x64 ?>
      <?define ProductName = "Product Name (64 bit)" ?>
      <?define Win64 = "yes" ?>
      <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
    <?else ?>
      <?define ProductName = "Product Name" ?>
      <?define Win64 = "no" ?>
      <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
    <?endif ?>
    
  3. Store the installation location in the registry, enabling upgrades to find the correct location. For example, if a user sets custom install directory.

     <Property Id="INSTALLLOCATION">
        <RegistrySearch Id="RegistrySearch" Type="raw" Root="HKLM" Win64="$(var.Win64)"
                  Key="Software\Company\Product" Name="InstallLocation" />
     </Property>
    

    Note: WiX guru Rob Mensching has posted an excellent blog entry which goes into more detail and fixes an edge case when properties are set from the command line.

    Examples using 1. 2. and 3.

    <?include $(sys.CURRENTDIR)\Config.wxi?>
    <Product ... >
      <Package InstallerVersion="200" InstallPrivileges="elevated"
               InstallScope="perMachine" Platform="$(var.Platform)"
               Compressed="yes" Description="$(var.ProductName)" />
    

    and

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="$(var.PlatformProgramFilesFolder)">
        <Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
    
  4. The simplest approach is always do major upgrades, since it allows both new installs and upgrades in the single MSI. UpgradeCode is fixed to a unique Guid and will never change, unless we don't want to upgrade existing product.

    Note: In WiX 3.5 there is a new MajorUpgrade element which makes life even easier!

  5. Creating an icon in Add/Remove Programs

    <Icon Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" />
    <Property Id="ARPPRODUCTICON" Value="Company.ico" />
    <Property Id="ARPHELPLINK" Value="http://www.example.com/" />
    
  6. On release builds we version our installers, copying the msi file to a deployment directory. An example of this using a wixproj target called from AfterBuild target:

    <Target Name="CopyToDeploy" Condition="'$(Configuration)' == 'Release'">
      <!-- Note we append AssemblyFileVersion, changing MSI file name only works with Major Upgrades -->
      <Copy SourceFiles="$(OutputPath)$(OutputName).msi" 
            DestinationFiles="..\Deploy\Setup\$(OutputName) $(AssemblyFileVersion)_$(Platform).msi" />
    </Target>
    
  7. Use heat to harvest files with wildcard (*) Guid. Useful if you want to reuse WXS files across multiple projects (see my answer on multiple versions of the same product). For example, this batch file automatically harvests RoboHelp output.

    @echo off  
    robocopy ..\WebHelp "%TEMP%\WebHelpTemp\WebHelp" /E /NP /PURGE /XD .svn  
    "%WIX%bin\heat" dir "%TEMP%\WebHelp" -nologo -sfrag -suid -ag -srd -dir WebHelp -out WebHelp.wxs -cg WebHelpComponent -dr INSTALLLOCATION -var var.WebDeploySourceDir 
    

    There's a bit going on, robocopy is stripping out Subversion working copy metadata before harvesting; the -dr root directory reference is set to our installation location rather than default TARGETDIR; -var is used to create a variable to specify the source directory (web deployment output).

  8. Easy way to include the product version in the welcome dialog title by using Strings.wxl for localization. (Credit: saschabeaumont. Added as this great tip is hidden in a comment)

    <WixLocalization Culture="en-US" xmlns="http://schemas.microsoft.com/wix/2006/localization">
        <String Id="WelcomeDlgTitle">{\WixUI_Font_Bigger}Welcome to the [ProductName] [ProductVersion] Setup Wizard</String>
    </WixLocalization>
    
  9. Save yourself some pain and follow Wim Coehen's advice of one component per file. This also allows you to leave out (or wild-card *) the component GUID.

  10. Rob Mensching has a neat way to quickly track down problems in MSI log files by searching for value 3. Note the comments regarding internationalization.

  11. When adding conditional features, it's more intuitive to set the default feature level to 0 (disabled) and then set the condition level to your desired value. If you set the default feature level >= 1, the condition level has to be 0 to disable it, meaning the condition logic has to be the opposite to what you'd expect, which can be confusing :)

    <Feature Id="NewInstallFeature" Level="0" Description="New installation feature" Absent="allow">
      <Condition Level="1">NOT UPGRADEFOUND</Condition>
    </Feature>
    <Feature Id="UpgradeFeature" Level="0" Description="Upgrade feature" Absent="allow">
      <Condition Level="1">UPGRADEFOUND</Condition>
    </Feature>
    

关于在“添加/删除程序”中添加图标,这正是我正在寻找的。你把那三行代码放在哪里?因为太棒了,所以给你点赞。 - Everett
我倾向于将它们放在 <Package> 元素之后(显然在其下方)。请查看模式以确保有效性 http://wix.sourceforge.net/manual-wix3/schema_index.htm - si618
+1,真希望我能点+100,这是我偶然发现的Wix信息中最有用的一部分。 - Tim Long
感谢Tim!Rob Mensching、Bob Arson、Wim Coehen和其他人分享他们的知识,值得赞扬。 - si618

38

检查IIS是否已安装:

<Property Id="IIS_MAJOR_VERSION">
    <RegistrySearch Id="CheckIISVersion" Root="HKLM" Key="SOFTWARE\Microsoft\InetStp" Name="MajorVersion" Type="raw" />
</Property>

<Condition Message="IIS must be installed">
    Installed OR IIS_MAJOR_VERSION
</Condition>

如何检查Vista+操作系统上是否安装了IIS 6 Metabase兼容性?

<Property Id="IIS_METABASE_COMPAT">
    <RegistrySearch Id="CheckIISMetabase" Root="HKLM" Key="SOFTWARE\Microsoft\InetStp\Components" Name="ADSICompatibility" Type="raw" />
</Property>

<Condition Message="IIS 6 Metabase Compatibility feature must be installed">
    Installed OR ((VersionNT &lt; 600) OR IIS_METABASE_COMPAT)
</Condition>

34

将所有 ID 放在不同的命名空间中

  • 特性使用以 F. 开头,例如:F.Documentation、F.Binaries、F.SampleCode。
  • 组件使用以 C. 开头,例如:C.ChmFile、C.ReleaseNotes、C.LicenseFile、C.IniFile、C.Registry。
  • 自定义操作使用以 CA. 开头,例如:CA.LaunchHelp、CA.UpdateReadyDlg、CA.SetPropertyX。
  • 文件使用以 Fi. 开头。
  • 目录使用以 Di. 开头。
  • 等等。

我发现这有助于更好地跟踪各种类别中的所有 ID。


我不使用命名空间,但我会附加ID;例如:ExamplesFeature,ChmFileComponent。我想我喜欢打字;-) - dvdvorle

25

非常好的问题。我希望能看到一些最佳实践。

我有很多要分发的文件,所以我将我的项目分成了几个wxs源文件。

我有一个顶级源文件,我称之为Product.wxs,它基本上包含了安装的结构,但不包括实际的组件。这个文件有几个部分:

<Product ...>
  <Package ...>
    <Media>... 
   <Condition>s ...
   <Upgrade ..>
   <Directory> 
        ...
   </Directory>
   <Feature>
      <ComponentGroupRef ... > A bunch of these that
   </Feature>
   <UI ...>
   <Property...>
   <Custom Actions...>
   <Install Sequences....
  </Package>
</Product>

其余的.wix文件由Fragments组成,其中包含ComponentGroups,在Product.wxs中的Feature标签中引用。我的项目包含一个很好的逻辑文件分组,我将其分发出去。

<Fragment>
   <ComponentGroup>
     <ComponentRef>
     ....
    </ComponentGroup>
    <DirectoryRef>
      <Component... for each file
      .... 
    </DirectoryRef>
</Fragment>

这并不完美,我的面向对象感觉有点敏感,因为片段必须引用Product.wxs文件中的名称(例如DirectoryRef),但我发现将其维护在单个大源文件中更容易。

我很想听听对此的评论,或者如果有人有好的建议也欢迎!


我们的设置也非常类似于这种方法。这样做很好,因为我们可以使用我们相当于Products.wxs的基本设置来适用于各种产品。 - si618
@Peter Tate:你的蜘蛛感很准确。请看我有关目录别名的答案。 - Wim Coenen
我采用相同的方法:Product.wxs与布局是静态的,而构建任务(heat.exe)生成我的Content.wxs文件。 - timvw

20

在退出对话框中添加复选框,以启动应用程序或帮助文件。

...

<!-- CA to launch the exe after install -->
<CustomAction Id          ="CA.StartAppOnExit"
              FileKey     ="YourAppExeId"
              ExeCommand  =""
              Execute     ="immediate"
              Impersonate ="yes"
              Return      ="asyncNoWait" />

<!-- CA to launch the help file -->
<CustomAction Id         ="CA.LaunchHelp"
              Directory  ="INSTALLDIR"
              ExeCommand ='[WindowsFolder]hh.exe IirfGuide.chm'
              Execute    ="immediate"
              Return     ="asyncNoWait" />

<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT"
          Value="Launch MyApp when setup exits." />

<UI>
  <Publish Dialog  ="ExitDialog"
           Control ="Finish"
           Order   ="1"
           Event   ="DoAction"
           Value   ="CA.StartAppOnExit">WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT</Publish>
</UI>

如果您按照这种方式操作,"标准"外观可能不太正确。复选框始终具有灰色背景,而对话框是白色的:

alt文本 http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3/images/exit_dlg_1.gif

解决此问题的一种方法是指定自定义的ExitDialog,并使用位置不同的复选框。这种方法可以解决问题,但似乎需要花费很多工作来改变一个控件的颜色。另一种解决相同问题的方法是后处理生成的MSI文件,更改该特定CheckBox控件在Control表中的X、Y字段。JavaScript代码如下:

var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql = "UPDATE `Control` SET `Control`.`Height` = '18', `Control`.`Width` = '170'," +
          " `Control`.`Y`='243', `Control`.`X`='10' " +
          "WHERE `Control`.`Dialog_`='ExitDialog' AND " + 
          "  `Control`.`Control`='OptionalCheckBox'";
var view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();
在使用cscript.exe作为命令行脚本运行此代码(在使用light.exe生成MSI后)将产生一个外观更专业的ExitDialog。 alt text http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3/images/exit_dlg_2.gif

哈!不是我的博客。我也读了它。我在上面的文本中有一个博客条目的链接。但他们的做法和我不一样。我更喜欢我的方式。 - Cheeso
1
感谢提供这个 JS,非常有帮助!在 wxs 文件中,我需要将 WIXUI_EXITDIALOGOPTIONALCHECKBOX 替换为 <Publish> 内的 WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed - Alexander Kojevnikov
有没有办法默认选中复选框? - Alek Davis
为了默认选中复选框,我使用了以下代码:<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>。 - Alek Davis
啊,是的,通过提出这个JS,我的假设是人们将使用cscript.exe解释器。 - Cheeso
显示剩余2条评论

18

使用相同的源文件创建Live、Test、Training版本

简而言之:为每个安装程序创建唯一的UpgradeCode,并自动定义每个安装程序的Guid的第一个字符,剩余31个字符保持唯一。

前提条件

假设条件

  • 使用WiX变量定义UpgradeCode、ProductName、InstallName。
  • 您已经拥有一个可用的安装程序。在您拥有可用的安装程序之前,不要尝试此操作。
  • 您的所有组件都保留在一个文件(Components.wxs)中。如果您有多个文件,此过程仍将起作用,但需要做更多的工作。

目录结构

  • Setup.Library
    • 所有wxs文件(组件、功能、UI对话框等)
    • Common.Config.wxi(ProductCode="*"、ProductVersion、PlatformProgramFilesFolder等)
  • Setup.Live(wixproj)
    • 使用“添加现有项”->“作为链接添加”(Visual Studio中添加按钮旁边的小向下箭头按钮)链接所有Setup.Library文件
    • Config.wxi(具有唯一的UpgradeCode、ProductName、InstallName等)
  • Setup.Test,...
    • 与Live相同,但Config.wxi针对Test环境进行了配置。

过程

  • 创建Setup.Library目录,并将现有项目中的所有wxs和wxi文件(除了Config.wxi)移动到其中。
  • 像普通的wixproj一样创建Setup.Live、Setup.Test等。
  • 在Setup.Live等的wixproj中添加BeforeBuild目标,执行MSBuild Community Task FileUpdate来修改Guids(我使用A表示Live,B表示Test,C表示Training)。
  • 添加AfterBuild目标以将Components.wxs Guids恢复为0。
  • 使用Orca验证每个MSI中的每个组件是否具有修改后的Guid。
  • 验证是否恢复了原始的Guid。
  • 验证每个MSI是否正确地安装(和升级)了正确的产品和位置。

Config.wxi示例

<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Upgrade code should not change unless you want to install 
     a new product and have the old product remain installed, 
     that is, both products existing as separate instances. -->
<?define UpgradeCode = "YOUR-GUID-HERE" ?>

<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
  <!-- Product name as you want it to appear in Add/Remove Programs-->
  <?define ProductName = "Foo 64 Bit [Live]" ?>
<?else ?>
  <?define ProductName =  "Foo [Live]" ?>
<?endif ?>

<!-- Directory name used as default installation location -->
<?define InstallName = "Foo [Live]" ?>

<!-- Registry key name used to store installation location -->
<?define InstallNameKey = "FooLive" ?>

<?define VDirName = "FooLive" ?>
<?define AppPoolName = "FooLiveAppPool" ?>
<?define DbName = "BlahBlahLive" ?>
</Include>

示例 Config.Common.wxi

<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Auto-generate ProductCode for each build, release and upgrade -->
<?define ProductCode = "*" ?>

<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define ProductVersion = "1.0.0.0" ?>

<!-- Minimum version supported if product already installed and this is an upgrade -->
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define MinimumUpgradeVersion = "0.0.0.0" ?>

<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
   <?define Win64 = "yes" ?>
   <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
   <?define Win64 = "no" ?>
   <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>

<?define ProductManufacturer = "Foo Technologies"?>

<!-- Decimal Language ID (LCID) for the Product. Used for localization. -->
<?define ProductLanguage = "1033" ?>

<?define WebSiteName = "DefaultWebSite" ?>
<?define WebSitePort = "80" ?>

<?define DbServer = "(local)" ?>
</Include>

示例Components.wxs

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <!-- The pre-processor variable which allows the magic to happen :) -->
  <?include $(sys.CURRENTDIR)\Config.wxi?>
  <?include ..\Setup.Library\Config.Common.wxi?>
  <Fragment Id="ComponentsFragment">
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="$(var.PlatformProgramFilesFolder)">
        <Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
          <Component Id="ProductComponent" Guid="0XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" KeyPath="yes">
          ...

注意:我现在建议在组件中省略Guid属性(相当于*),每个组件使用一个文件,并将文件设置为关键路径。这样就不需要调用下面显示的ModifyComponentsGuidsRevertComponentsGuids目标。虽然这可能对您的所有组件都不可行。

示例 Setup.Live.wixproj

<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<Target Name="BeforeBuild">
  <CallTarget Targets="ModifyComponentsGuids" />
</Target>
<Target Name="AfterBuild">
  <CallTarget Targets="RevertComponentsGuids" />
</Target>
<!-- Modify the first character of every Guid to create unique value for Live, Test and Training builds -->
<Target Name="ModifyComponentsGuids">
  <FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid=&quot;([a-f]|[A-F]|\d)" ReplacementText="Guid=&quot;A" />
</Target>
<!-- Revert the first character of every Guid back to initial value -->
<Target Name="RevertComponentsGuids">
  <FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid=&quot;([a-f]|[A-F]|\d)" ReplacementText="Guid=&quot;0" />
</Target>

最后的想法

  • 对于为同一安装程序创建不同合并模块(Live、Test等作为功能)的过程,此方法也适用。我选择使用不同的安装程序,因为这似乎是更安全的选择,如果您只针对不同的合并模块使用功能,则有更大的风险,即有人可能会升级Live而不是Training。
  • 如果您的MSI用于执行升级以及新安装,即仅采用主要升级方法,并在注册表中保存安装位置,请记得为每个安装创建一个密钥名称变量。
  • 我们还在每个Config.wxi中创建变量,以便为每个安装程序启用唯一的虚拟目录名称、应用程序池、数据库名称等。

更新1:自动生成组件Guid可以通过为每个文件创建带有Guid =“*”的组件并将文件设置为关键路径来消除调用FileUpdate任务的需要。

更新2:我们遇到的问题之一是,如果您没有自动生成组件Guid,并且生成失败,则需要手动删除临时文件。

更新3:找到了一种方法来消除对svn:externals和临时文件创建的依赖。如果您无法使用通配符匹配Guid,则这使生成过程更具弹性,并且在light或candle中出现构建故障时更加稳定。

更新4:WiX 3.0+支持使用实例转换进行多个实例,绝对值得一看。


+1 针对 MSBuild Community Tasks 的推荐,很喜欢那个工具包。 - BozoJoe

17

2
如果我们可以在Wix中启用日志记录而不是通过命令行启用,那将会更好。+1 - si618
3
WiX可以设置MsiLogging属性,但仅受Windows Installer 4.0及以上版本支持。 - Rob Mensching
非常感谢"Wix先生"。我得去检查一下。 - Terrance

17

使用JavaScript CustomActions因其易用性而变得如此简单

有人说JavaScript不适合用于MSI CustomActions。理由是:难以调试,难以使之可靠。但我并不同意。它不难调试,确实比C++容易。只是不同而已。我发现用JavaScript编写CustomActions非常简单,比使用C++要容易得多。速度也快得多。同样可靠。

只有一个缺点:JavaScript CustomActions可以通过Orca提取,而C/C++ CA需要反向工程。如果您认为您的安装程序是受保护的知识产权,那么您将希望避免使用脚本。

如果您使用脚本,您只需要从一些结构开始。以下内容可以帮助您入门。


JavaScript自定义操作的“模板”代码:

//
// CustomActions.js 
// 
// Template for WIX Custom Actions written in Javascript.
// 
// 
// Mon, 23 Nov 2009  10:54
// 
// ===================================================================


// http://msdn.microsoft.com/en-us/library/sfw6660x(VS.85).aspx
var Buttons = {
        OkOnly           : 0,
        OkCancel         : 1,
        AbortRetryIgnore : 2,
        YesNoCancel      : 3
};

var Icons = {
        Critical         : 16,
        Question         : 32,
        Exclamation      : 48,
        Information      : 64
};

var MsgKind = {
        Error            : 0x01000000,
        Warning          : 0x02000000,
        User             : 0x03000000,
        Log              : 0x04000000
};

// http://msdn.microsoft.com/en-us/library/aa371254(VS.85).aspx
var MsiActionStatus = {
        None             : 0,
        Ok               : 1, // success
        Cancel           : 2,
        Abort            : 3,
        Retry            : 4, // aka suspend?
        Ignore           : 5  // skip remaining actions; this is not an error.
};


function MyCustomActionInJavascript_CA() {
    try {
        LogMessage("Hello from MyCustomActionInJavascript");
        // ...do work here...
        LogMessage("Goodbye from MyCustomActionInJavascript");
    }
    catch (exc1) {
        Session.Property("CA_EXCEPTION") = exc1.message ;
        LogException(exc1);
        return MsiActionStatus.Abort;
    }
    return MsiActionStatus.Ok;
}

// Pop a message box.  also spool a message into the MSI log, if it is enabled. 
function LogException(exc) {
    var record = Session.Installer.CreateRecord(0);
    record.StringData(0) = "CustomAction: Exception: 0x" + decimalToHexString(exc.number) + " : " + exc.message;
    Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}


// spool an informational message into the MSI log, if it is enabled. 
function LogMessage(msg) {
    var record = Session.Installer.CreateRecord(0);
    record.StringData(0) = "CustomAction:: " + msg;
    Session.Message(MsgKind.Log, record);
}


// http://msdn.microsoft.com/en-us/library/d5fk67ky(VS.85).aspx
var WindowStyle = {
    Hidden : 0,
    Minimized : 1,
    Maximized : 2
};

// http://msdn.microsoft.com/en-us/library/314cz14s(v=VS.85).aspx
var OpenMode = {
    ForReading : 1,
    ForWriting : 2,
    ForAppending : 8
};

// http://msdn.microsoft.com/en-us/library/a72y2t1c(v=VS.85).aspx
var SpecialFolders = {
    WindowsFolder : 0, 
    SystemFolder :  1, 
    TemporaryFolder : 2
};

// Run a command via cmd.exe from within the MSI
function RunCmd(command)
{
    var wshell = new ActiveXObject("WScript.Shell");
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var tmpdir = fso.GetSpecialFolder(SpecialFolders.TemporaryFolder);
    var tmpFileName = fso.BuildPath(tmpdir, fso.GetTempName());

    LogMessage("shell.Run("+command+")");

    // use cmd.exe to redirect the output
    var rc = wshell.Run("%comspec% /c " + command + "> " + tmpFileName, WindowStyle.Hidden, true);
    LogMessage("shell.Run rc = "  + rc);

    // here, optionally parse the output of the command 
    if (parseOutput) {
        var textStream = fso.OpenTextFile(tmpFileName, OpenMode.ForReading);
        while (!textStream.AtEndOfStream) {
            var oneLine = textStream.ReadLine();
            var line = ParseOneLine(oneLine);
                ...
        }
        textStream.Close();
    }

    if (deleteOutput) {
        fso.DeleteFile(tmpFileName);
    }

    return {
        rc : rc,
        outputfile : (deleteOutput) ? null : tmpFileName
    };
}

接下来,使用类似以下的方式注册自定义操作:

<Fragment>
  <Binary Id="IisScript_CA" SourceFile="CustomActions.js" />

  <CustomAction Id="CA.MyCustomAction"
              BinaryKey="IisScript_CA"
              JScriptCall="MyCustomActionInJavascript_CA"
              Execute="immediate"
              Return="check" />
</Fragmemt>
当然可以插入多个JavaScript函数,以实现多个自定义操作。例如,我使用JavaScript对IIS进行了WMI查询,以获取现有网站列表,可以在其中安装ISAPI筛选器。然后使用此列表来填充稍后在UI序列中显示的列表框。非常简单。
在IIS7上,没有针对IIS的WMI提供程序,因此我使用shell.Run()方法调用appcmd.exe执行工作。容易。
相关问题:关于JavaScript CustomActions

2
+1 我发现DTF方法易于设置,但JavaScript也可能很有用。 - si618

12

我很惊讶没有人提到使用T4在构建过程中生成WXS文件。我是通过Henry Lee在New Age Solutions的博客了解到这一点的。

基本上,你需要创建一个自定义MSBuild任务来执行T4模板,而该模板会在Wix项目编译之前输出WXS文件。这允许你(根据实现方式)自动包含从其他解决方案编译的所有程序集的输出(这意味着你不再需要每次添加新程序集时都编辑wxs文件)。


2
+1,这真的很不错,我不太担心程序集,但我们的 Web 项目可能会在 aspx 页面和其他附属文件(如图片、CSS)方面出问题,这些文件被添加到项目中,但却没有包含在 WiX 中。 - si618
4
未来的访问者,Wix 3.5有一个名为heat.exe的工具,可以自动完成这种收集工作。 - Mrchief
@Mrchief - 我不相信Heat会捡起被复制到本地的引用程序集 - 不过这显然是计划在4.0中实现的。参考:http://sourceforge.net/tracker/index.php?func=detail&aid=2998492&group_id=105970&atid=642714 - Peter T. LaComb Jr.
热更新未能加载引用程序集。 - tofutim
使用T4生成WXS文件的一些好例子是什么? - tofutim

12

使用Heat.exe来处理庞大的安装程序并实现"史诗级的胜利"

扩展SiRobert-P有关heat的回答。

翻译: (使用heat避免手动输入项目中的单个文件,并自动化构建以实现整体更轻松的过程。)

WiX 2.0 Heat语法详解

对于更新的版本(与旧版本并没有太大区别,但有可能会有烦人的语法更改....),请从cmd.exe进入Heat所在目录,只需键入heat即可,但如果需要帮助新版本,我这里有一个示例。

将以下内容添加到Visual Studio 2010中的生成事件中。
(右键单击项目->属性->生成事件->前生成事件)

$(WIX)bin\heat.exe" dir "$(EnviromentVariable)" -cg GroupVariable -gg -scom -sreg -sfrag - srd -dr INSTALLLOCATION -var env.LogicPath -out "$(FragmentDir)\FileName.wxs

-gg 

运行heat时生成Guid(如上面的命令执行时)

-scom 

不获取“COM文件”

-sreg 

不获取“注册表文件”

-sfrag 

不获取“片段”

-srd 

不获取“根目录”

dir

dir表示您希望Heat查找的文件夹

"$(EnviromentVariable)"

您将在(Right click project, Go to properties)项目属性->生成部分中添加到预处理器变量中的变量名称(假定使用Visual Studio 2010)

示例:
EnviromentVariable=C:\Project\bin\Debug;
不要使用双引号,但以分号结尾

-cg GroupVariable 

将被引用从创建的片段到主wxs文件中的ComponentGroup

FragmentDir

输出wxs片段将存储的片段目录

FileName.wxs

文件名

完整教程在这里,非常有帮助

第1部分 第2部分


有另一种用途稍有不同但很有用的工具:Paraffin (http://www.wintellect.com/CS/blogs/jrobbins/archive/2010/03/10/4107.aspx)。 - ralf.w.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接