WiX技巧和提示

我们已经使用WiX有一段时间了,尽管人们对它的易用性有一些抱怨,但它运行得相当不错。我想要的是有用的建议:

  • 设置WiX项目(布局、引用、文件模式)
  • 将WiX集成到解决方案中,并构建/发布流程
  • 为新的安装和升级配置安装程序
  • 任何你想分享的好的WiX技巧
223534 次浏览
  • 我们将产品版本显示在GUI的第一个屏幕的某个位置(很小)。因为人们每次都倾向于在选择正确版本时犯错误。(这让我们开发人员找了很长时间。)

  • 我们已经设置了TFSBuild来生成转换(。MST文件)为我们不同的环境配置。(我们知道需要部署到的所有环境)。

由于Grant Holliday的原始博客帖子已经关闭,我复制粘贴了它的内容:


MSBuild任务从xml3月11日2008生成MSI转换文件

在我之前的文章中,我描述了如何使用MSI Transform (*.mst)文件将特定于环境的配置设置从通用MSI包中分离出来。

尽管这为您的配置提供了一定程度的灵活性,但Transform文件有两个缺点:

  1. 它们是二进制格式的
  2. 您不能“编辑”或“查看”转换文件。您必须应用它或重新创建它,以查看它包含哪些更改。

幸运的是,我们可以使用Microsoft Windows安装程序对象库(c:windowssystem32msi.dll)打开MSI“数据库”并创建转换文件。

积分再次转到舍甫丘克-从MSI到WiX -第7部分 -使用转换自定义安装,向我们展示如何用VbScript实现这一点。基本上,我所做的就是以Alex为例,使用Interop.WindowsInstaller.dll实现了一个MSBuild任务。 MSBuild Task

. MSBuild Task

下载源代码&示例transforms.xml here (~7Kb压缩VS2008解决方案)


奇妙的问题。我希望看到一些最佳实践的展示。

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

我有一个顶级源文件,我称之为产品。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文件由包含在Product.wxs的Feature标签中引用的componentgroup的片段组成。我的项目包含我分发的文件的一个很好的逻辑分组

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

这并不完美,我的OO蜘蛛感觉有点刺痛,因为片段必须引用产品中的名称。wxs文件(例如DirectoryRef),但我发现维护一个大的源文件更容易。

我很想听到关于这一点的评论,或者如果有人有任何好的建议!

  1. 将变量保存在单独的wxi包含文件中。允许重用,变量可以更快地找到,并且(如果需要)允许更容易地由外部工具操作。

  2. 定义x86和x64版本的平台变量

    <!-- 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>
    

    请注意: WiX大师Rob高洁的人已经发布了一个优秀的博客文章,其中更详细地讨论了从命令行设置属性时的边界情况。

    使用1的示例。2. 和3。

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

    而且

    <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
    

    有一点正在进行,robocopy在收集之前剥离Subversion工作副本元数据;-dr根目录引用被设置为我们的安装位置,而不是默认的TARGETDIR;-var用于创建一个变量来指定源目录(web部署输出)

  8. 使用字符串在欢迎对话框标题中包含产品版本的简单方法。WXL用于本地化。(来源:saschabeaumont。添加为这个伟大的提示隐藏在评论中)

    <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>
    

创建现场、测试、培训……使用相同源文件的版本。

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

先决条件

假设

  • WiX变量用于定义UpgradeCode、ProductName、InstallName。
  • 您已经有了一个工作的安装程序。你不动手我是不会动手的。
  • 所有组件都保存在一个文件(Components.wxs)中。如果你有多个文件,这个过程是有效的,只是有更多的工作要做。

目录结构

  • <强>设置。图书馆
    • 所有wxs文件(组件,功能,UI对话框,…)
    • Common.Config.wxi (ProductCode="*", ProductVersion, PlatformProgramFilesFolder,…)
    • 李< / ul > < / > <李> 设置。生活 (wixproj)
      • 链接所有安装程序。“添加为链接”(Visual Studio中“添加”按钮旁边的向下箭头按钮)
      • 配置。wxi(有唯一的UpgradeCode, ProductName, InstallName,…)
      • 李< / ul > < / > <李> 设置。测试,……
        • 按live但配置。wxi为测试环境配置。
        • 李< / ul > < / >

        过程

        • 创建设置。并从现有项目中移动所有wxs和wxi文件(Config.wxi除外)。
        • 创建设置。生活,设置。测试,等按照正常wixproj。
        • 在安装程序中的wixproj中添加BeforeBuild目标。执行MSBuild社区任务FileUpdate来修改Guids(我使用A用于Live, B用于测试和C用于培训)
        • 添加AfterBuild目标以恢复组件。wxs Guids返回0。
        • 与Orca验证每个MSI中的每个组件都有修改过的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属性放在Component(相当于*)之外,每个组件使用一个文件,并将该文件设置为小键盘。这样就不需要调用下面所示的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,如果他们在同一个机器上,你只是使用不同合并模块的功能,那么他们升级Live的风险更大。
        • 如果您使用MSI执行升级以及新安装,即主要的升级方法,并且您将安装位置保存在注册表中,请记住为每个安装创建一个键名变量。
        • 我们还在每个Config中创建变量。为每个安装程序启用唯一的虚拟目录名称、应用程序池、数据库名称等等。

        更新1: 自动生成组件向导如果你为每个文件创建Guid="*"的组件,将文件设置为小键盘,则不需要调用FileUpdate任务。

        我们遇到的一个问题是,如果你不自动生成组件Guid,构建失败,那么临时文件需要手动删除。

        更新3:找到了一种方法来消除对svn的依赖:externals和临时文件创建。这使得构建过程更具弹性(如果您不能通配符您的guid,这是最好的选择),并且在灯光或蜡烛中构建失败时不那么脆弱。

        WiX 3.0+支持使用实例转换的多个实例,当然也值得一看。

这是一个很好的结构,但根据我的经验,我想知道你如何解决这些情况:

答:你的安装似乎都落在了同一个目的地。如果用户需要同时安装所有3个版本,您的进程是否允许这样做。它们能明确地说出它们正在触发的每个可执行文件的哪个版本吗?

B.如何处理TEST和/或TRAINING中存在但LIVE中还没有的新文件?

Peter Tate已经展示了如何在单独的wix片段中定义可重用的ComponentGroup定义。一些与此相关的额外技巧:

目录别名

组件组片段不需要知道主产品wxs定义的目录。在你的组件组片段中,你可以这样描述一个文件夹:

<DirectoryRef Id="component1InstallFolder">
...
</DirectoryRef>

然后主产品可以别名它的一个目录(例如。"productInstallFolder")像这样:

<Directory Id="productInstallFolder" Name="ProductName">
<!-- not subfolders (because no Name attribute) but aliases for parent! -->
<Directory Id="component1InstallFolder"/>
<Directory Id="component2InstallFolder"/>
</Directory>

依赖关系图

ComponentGroup元素可以包含ComponentGroupRef子元素。如果您有大量可重用组件,并且它们之间有复杂的依赖关系图,那么这是非常棒的。你只需要为每个组件在自己的片段中建立一个ComponentGroup,并像这样声明依赖项:

<ComponentGroup Id="B">
<ComponentRef Id="_B" />
<ComponentGroupRef Id="A">
</ComponentGroup>

如果您现在在设置中引用组件组“B”,因为它是应用程序的直接依赖项,那么它将自动拉入组件组“a”,即使应用程序作者从未意识到它是“B”的依赖项。只要你没有任何循环依赖,它就“可以工作”。

可重用wixlib

如果您使用lit.exe将大池-o-可重用组件编译为可重用的wixlib,则上述依赖关系图的想法效果最好。在创建应用程序设置时,可以像引用wixobj文件一样引用这个wixlib。exe链接器将自动消除没有被主产品wxs文件“拉入”的任何片段。

检查是否安装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+上是否安装了iis6元数据库兼容性:

<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>

包括COM对象:

heat生成所有大多数(如果不是全部)注册表项和它们所需的其他配置。喜乐!

包括托管COM对象(又名。net或c# COM对象)

在托管COM对象上使用heat将给你一个几乎完整的wix文档。

如果你不需要GAC中可用的库(即全局可用:大多数时候你的. net程序集不需要这个库——如果它不是一个共享库,你可能做错了什么),你将需要确保将CodeBase注册表项更新为[#ComponentName]。如果你打算将它安装到GAC(例如,你已经创建了一些每个人都想使用的新很棒的公共库),你必须删除这个条目,并向File元素添加两个新属性:AssemblyKeyPath。Assembly应设置为".net", KeyPath应设置为"yes"。

然而,某些环境(特别是带有托管内存的环境,如脚本语言)也需要访问Typelib。确保在typelib上运行heat,并包括它。heat将生成所有需要的注册表项。多酷啊!

这是一种帮助大型web项目验证部署的文件数量与内置到MSI(或合并模块)的文件数量匹配的方法。我刚刚在我们的主应用程序(仍在开发中)上运行了自定义MSBuild任务,它拾起了相当多的丢失的文件,主要是图像,但一些javascript文件已经滑过!

这种方法(通过连接到WiX项目的AfterBuild目标来窥视MSI的文件表)可以适用于其他类型的应用程序,在这些应用程序中,您可以访问所需文件的完整列表。

环境变量

在将Wxs文档编译为wixobj代码时,可以使用环境变量来确定各种信息。例如,假设您想要更改项目中包含的文件。让我们假设你有一个名为RELEASE_MODE的环境变量,你在构建MSI之前设置了它(无论是用脚本还是手动设置,都没关系)在你的wix源代码中,你可以这样做:

<define FILESOURCE = c:\source\output\bin\$(env.RELEASE_MODE) >

然后在稍后的代码中,在适当的地方使用它来动态地更改WXS文档,例如:

<Icon Id="myicon.ico" SourceFile="$(var.FILESOURCE)" />

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

...

<!-- 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 text http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3/images/exit_dlg_1.gif

一种解决方法是使用不同位置的复选框指定自己的自定义ExitDialog。这是可行的,但似乎很多工作只是改变一个控件的颜色。另一种解决相同问题的方法是对生成的MSI进行后处理,以更改控制表中特定CheckBox控件的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();

在MSI生成后(从light.exe)运行这段代码作为命令行脚本(使用cscript.exe)将产生一个看起来更专业的ExitDialog:

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

修改“准备好安装了吗?”对话框(又名VerifyReadyDlg),以提供所做选择的摘要。

它看起来像这样:
Alt文本http://i46.tinypic.com/s4th7t.jpg < / p >

用Javascript CustomAction来做这个:


Javascript代码:

// http://msdn.microsoft.com/en-us/library/aa372516(VS.85).aspx
var MsiViewModify =
{
Refresh          : 0,
Insert           : 1,
Update           : 2,
Assign           : 3,
Replace          : 4,
Merge            : 5,
Delete           : 6,
InsertTemporary  : 7,   // cannot permanently modify the MSI during install
Validate         : 8,
ValidateNew      : 9,
ValidateField    : 10,
ValidateDelete   : 11
};




// 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 UpdateReadyDialog_CA(sitename)
{
try
{
// can retrieve properties from the install session like this:
var selectedWebSiteId = Session.Property("MSI_PROPERTY_HERE");


// can retrieve requested feature install state like this:
var fInstallRequested   = Session.FeatureRequestState("F.FeatureName");


var text1 = "This is line 1 of text in the VerifyReadyDlg";


var text2 = "This is the second line of custom text";


var controlView     = Session.Database.OpenView("SELECT * FROM Control");
controlView.Execute();


var rec             = Session.Installer.CreateRecord(12);
rec.StringData(1)   = "VerifyReadyDlg";    // Dialog_
rec.StringData(2)   = "CustomVerifyText1"; // Control - can be any name
rec.StringData(3)   = "Text";              // Type
rec.IntegerData(4)  = 25;                  // X
rec.IntegerData(5)  = 60;                  // Y
rec.IntegerData(6)  = 320;                 // Width
rec.IntegerData(7)  = 85;                  // Height
rec.IntegerData(8)  = 2;                   // Attributes
rec.StringData(9)   = "";                  // Property
rec.StringData(10)  = vText1;              // Text
rec.StringData(11)  = "";                  // Control_Next
rec.StringData(12)  = "";                  // Help
controlView.Modify(MsiViewModify.InsertTemporary, rec);


rec                 = Session.Installer.CreateRecord(12);
rec.StringData(1)   = "VerifyReadyDlg";    // Dialog_
rec.StringData(2)   = "CustomVerifyText2"; // Control - any unique name
rec.StringData(3)   = "Text";              // Type
rec.IntegerData(4)  = 25;                  // X
rec.IntegerData(5)  = 160;                 // Y
rec.IntegerData(6)  = 320;                 // Width
rec.IntegerData(7)  = 65;                  // Height
rec.IntegerData(8)  = 2;                   // Attributes
rec.StringData(9)   = "";                  // Property
rec.StringData(10)  = text2;               // Text
rec.StringData(11)  = "";                  // Control_Next
rec.StringData(12)  = "";                  // Help
controlView.Modify(MsiViewModify.InsertTemporary, rec);


controlView.Close();
}
catch (exc1)
{
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException("UpdatePropsWithSelectedWebSite", exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}




function LogException(loc, exc)
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "Exception {" + loc + "}: " + exc.number + " : " + exc.message;
Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}

声明Javascript CA:

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


<CustomAction Id="CA.UpdateReadyDialog"
BinaryKey="IisScript_CA"
JScriptCall="UpdateReadyDialog_CA"
Execute="immediate"
Return="check" />
</Fragment>

将CA连接到按钮上。在这个例子中,当从CustomizeDlg中单击Next时,CA将被触发:

<UI ...>
<Publish Dialog="CustomizeDlg" Control="Next" Event="DoAction"
Value="CA.UpdateReadyDialog" Order="1"/>
</UI>

相关SO问题:我怎么能设置,在运行时,文本显示在VerifyReadyDlg?

将所有id保存在单独的名称空间中

  • 特性以F.开头,例如:F.Documentation, F.Binaries, F.SampleCode。
  • 组件以C. Ex开头:C.ChmFile, C.ReleaseNotes, C.LicenseFile, C.IniFile, C.Registry
  • CustomActions是CA. Ex: CA.LaunchHelp, CA.UpdateReadyDlg, CA.SetPropertyX
  • 文件是Fi.
  • 目录是Di.
  • 等等。

我发现这对追踪不同类别的各种本我非常有帮助。

修复ProgressDlg,使其正确显示。

我将安装程序的字体大小从8增加到10,以使字体在高分辨率显示器上更人性化、更实用。我用这个XML魔法做到这一点:

<UI Id="MyCustomUI">
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="10" />
<TextStyle Id="WixUI_Font_Big"    FaceName="Tahoma" Size="12" />
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="14" />
<TextStyle Id="WixUI_Font_Title"  FaceName="Tahoma" Size="12" Bold="yes" />


<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
</UI>

但这意味着ProgressDlg将无法正常显示。这是一个显示安装进度的程序,就在最后。ActionText被剪切,因此g和j等字母的下移不显示。在后处理Javascript中,通过调整Progressdialog中各种控件的大小和位置来解决这个问题。生成MSI后运行这个脚本:

var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);


// The text on the exit dialog is too close to the title.  This
// step moves the text down from Y=70 to Y=90, about one line.
sql = "UPDATE `Control` SET `Control`.`Y` = '90' " +
"WHERE `Control`.`Dialog_`='ExitDialog' AND `Control`.`Control`='Description'";
view = database.OpenView(sql);
view.Execute();
view.Close();


// The progressbar is too close to the status text on the Progress dialog.
// This step moves the progressbar down from Y=115 to Y=118, about 1/3 line.
sql = "UPDATE `Control` SET `Control`.`Y` = '118' " +
"WHERE `Control`.`Dialog_`='ProgressDlg' AND `Control`.`Control`='ProgressBar'";
view = database.OpenView(sql);
view.Execute();
view.Close();


// The StatusLabel and ActionText controls are too short on the Progress dialog,
// which means the bottom of the text is cut off.  This step
// increases the height from 10 to 16.
sql = "UPDATE `Control` SET `Control`.`Height` = '16' " +
"WHERE `Control`.`Dialog_`='ProgressDlg' AND `Control`.`Control`='StatusLabel'";
view = database.OpenView(sql);
view.Execute();
view.Close();
sql = "UPDATE `Control` SET `Control`.`Height` = '16' " +
"WHERE `Control`.`Dialog_`='ProgressDlg' AND `Control`.`Control`='ActionText'";
view = database.OpenView(sql);
view.Execute();
view.Close();


database.Commit();

使用Javascript的CustomActions,因为它们非常简单

人们已经说过Javascript是错误的东西用于MSI CustomActions。给出的原因:难以调试,难以使其可靠。我不同意。调试它并不难,当然不比c++难。就是不一样。我发现用Javascript写CustomActions超级简单,比用c++简单多了。快得多。而且同样可靠。

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

如果使用脚本, 你只需要从一些结构开始。这里有一些开始。


CustomAction的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

我很惊讶没有人提到在构建期间使用T4生成WXS文件。我是通过Henry Lee @ 新时代解决方案了解到这一点的。

实际上,您创建了一个自定义MSBuild任务来执行T4模板,该模板在编译Wix项目之前输出WXS。这允许您(取决于您如何实现它)自动包含来自编译另一个解决方案的所有程序集输出(这意味着您不再需要在添加新程序集时编辑wxs)。

使用Heat.exe粉碎脸,并对痛苦的大安装造成“史诗Pwnage” < / b > < / p >

扩展如果是Robert-P的回答关于热。

翻译: (使用heat可以避免手动将单个文件输入到项目中,并且可以自动化构建,从而使整个过程更容易。)

详细介绍WiX 2.0热语法

对于新版本(不是所有的不同于旧版本,但有潜在的恼人的语法变化....),从cmd.exe进入目录Heat,只需输入Heat,但我有一个例子,如果需要,可以在这里帮助更新版本。

将以下内容添加到visual studio 2010中的构建事件。
(右键单击项目->属性->构建事件->预构建事件)
.
$(WIX)bin\heat.exe" dir "$(环境变量)"-cg GroupVariable -gg -scom -sreg -sfrag - srd -dr INSTALLLOCATION -varLogicPath out "$(FragmentDir)\FileName.wxs .
< / p >

-gg 

Generates Guids when heat is run(as in when you execute the command above)

-scom 

不要抓取“COM文件”

-sreg 

Dont grab "Registry Files"

-sfrag 

不要抓取“碎片”

-srd 

Dont grab the "root Dir"

dir

dir表示希望Heat在文件夹中查找

"$(EnviromentVariable)"

The name of the variable you would add to the Preprocessor variables in the (Right click project, Go to properties) project properties->Build section where it says Define preprocessor variables (assumes visual studio 2010)

Example:
EnviromentVariable=C:\Project\bin\Debug;
No double quotes but end with a semicolon

-cg GroupVariable 

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

FragmentDir

The fragment directory where the output wxs fragment will be stored

FileName.wxs

文件的名称

这里有完整的教程,非常有用

第一部分 第2部分

使用Msi诊断日志记录获取详细的故障信息

msiexec /i包。msi /l*v c:\Package.log
< / p >

哪里

Package.msi
是你的包的名称
c: \ Package.log
是你想要日志输出的地方

Msi错误码

< p > Wix介绍视频
哦和Random Wix介绍视频"WiX"Rob Mensching是“概念性的大图景”;有帮助的。 < / p >

当安装不允许卸载或重新安装且不能回滚时执行强制重新安装。

. VBscript脚本,用于覆盖由于某种原因没有卸载的安装
Dim objShell
set objShell = wscript.createObject("wscript.shell")


iReturn = objShell.Run("CMD /K MsiExec.exe /I ""C:\Users\TheUser\Documents\Visual Studio 2010\Projects\InstallationTarget\HelloInstaller\bin\Debug\HelloInstaller.msi"" REINSTALLMODE=vomus REINSTALL=ALL",,True)

把可以单独打补丁的组件放在它们自己的片段中

它适用于产品安装程序和补丁,如果你在一个片段中包含任何组件,你必须在该片段中包含组件的所有。在构建安装程序的情况下,如果您遗漏了任何组件引用,您将从light.exe得到一个链接错误。然而,当你创建一个补丁时,如果你在一个片段中包含一个组件引用,那么所有从该片段更改的组件将显示在你的补丁中。

是这样的:

<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent1" Guid="{C28843DA-EF08-41CC-BA75-D2B99D8A1983}" DiskId="1">
<File Id="SampleFile1" Source=".\$(var.Version)f\Sample1.txt" />
</Component>
</DirectoryRef>
</Fragment>


<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent2" Guid="{6CEA5599-E7B0-4D65-93AA-0F2F64402B22}" DiskId="1">
<File Id="SampleFile2" Source=".\$(var.Version)f\Sample2.txt" />
</Component>
</DirectoryRef>
</Fragment>


<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent3" Guid="{4030BAC9-FAB3-426B-8D1E-DC1E2F72C2FC}" DiskId="1">
<File Id="SampleFile3" Source=".\$(var.Version)f\Sample3.txt" />
</Component>
</DirectoryRef>
</Fragment>

而不是这样:

<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent1" Guid="{C28843DA-EF08-41CC-BA75-D2B99D8A1983}" DiskId="1">
<File Id="SampleFile1" Source=".\$(var.Version)\Sample1.txt" />
</Component>


<Component Id="SampleComponent2" Guid="{6CEA5599-E7B0-4D65-93AA-0F2F64402B22}" DiskId="1">
<File Id="SampleFile2" Source=".\$(var.Version)\Sample2.txt" />
</Component>


<Component Id="SampleComponent3" Guid="{4030BAC9-FAB3-426B-8D1E-DC1E2F72C2FC}" DiskId="1">
<File Id="SampleFile3" Source=".\$(var.Version)\Sample3.txt" />
</Component>
</DirectoryRef>
</Fragment>

此外,在使用WiX中的“使用纯WiX”主题进行修补时。CHM帮助文件,使用这个程序生成补丁:

torch.exe -p -xi 1.0\product.wixpdb 1.1\product.wixpdb -out patch\diff.wixmst
candle.exe patch.wxs
light.exe patch.wixobj -out patch\patch.wixmsp
pyro.exe patch\patch.wixmsp -out patch\patch.msp -t RTM patch\diff.wixmst

仅仅拥有产品的1.1版本是不够的。Wixpdb使用单独片段中的组件构建。所以一定要在发货前对产品进行正确的分段。

正在安装到C:\ProductName

一些应用程序需要安装到C:\ProductName或类似的地方,但99.9%(如果不是100%)的示例安装到C:\Program Files\CompanyName\ProductName

下面的代码可用于将TARGETDIR属性设置为C:驱动器的根(取自WiX-users列表):

<CustomAction Id="AssignTargetDir" Property="TARGETDIR" Value="C:\" Execute="firstSequence" />
<InstallUISequence>
<Custom Action="AssignTargetDir" Before="CostInitialize">TARGETDIR=""</Custom>
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="AssignTargetDir" Before="CostInitialize">TARGETDIR=""</Custom>
</InstallExecuteSequence>

注意:默认情况下,TARGETDIR 指向C:\!相反,它指向ROOTDRIVE,而ROOTDRIVE又指向空闲空间最大的驱动器根目录 (在这里看到的)——这并不一定是C:驱动器。可能有另一个硬盘驱动器,分区,或USB驱动器!

然后,在<Product ...>标记下面的某个地方,像往常一样,你需要以下目录标记:

<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="APPLICATIONFOLDER" Name="$(var.ProductName)">
<!-- your content goes here... -->
</Directory>
</Directory>

从Wix3.0及更高版本打印EULA

1)当你编译你的wix源代码时,light.exe必须在命令行引用WixUIExtension.dll。为此使用命令行开关-ext。

2)如果当你在WixUIExtension.dll中添加引用时,你的项目编译失败,这很可能是因为对话框id的冲突,即你的项目使用了与WixUIExtension.dll中一些标准对话框相同的对话框id,给你的对话框提供不同的id。这是一个很普遍的问题。

3)你的许可对话框必须有id为“LicenseText”的ScrollableText控件。Wix在打印时搜索该控件的名称。

<Control Id="LicenseText" Type="ScrollableText" X="20" Y="60" Width="330" Height="160" Sunken="yes" TabSkip="no">
<Text SourceFile="License.rtf" />
</Control>

以及指向自定义动作的PushButton

<Control Type="PushButton" Id="PrintButton" Width="57" Height="17" X="19" Y="244" Text="Print">
<Publish Event="DoAction" Value="PrintEula">1</Publish>
</Control>

4)使用Id="PrintEula"定义CustomAction,如下所示:

<CustomAction Id="PrintEula" BinaryKey="WixUIWixca" DllEntry="PrintEula" Return="ignore" Execute="immediate" />

注意:BinaryKey在Wix3.0和Wix2.0中是不同的,必须准确地为“WixUIWixca”(区分大小写)。

当用户按下按钮时,他/她将出现标准的选择打印机对话框,并能够从那里打印。

创建WIX的自定义动作,用托管代码(c#)编写,不带Votive

http://www.codeproject.com/KB/install/wixcustomaction.aspx < a href = " http://www.codeproject.com/KB/install/wixcustomaction.aspx " > < / >

编辑对话框

编辑对话框的一个很好的功能是使用版本为4.0.1.7090(或更高)的SharpDevelop。在这个工具的帮助下,一个独立的对话框(来自WiX源的wxs文件,如installdirdg .wxs)可以在设计视图中打开、预览和编辑。

而不是ORCA使用本月,这是一个查看MSI表的好工具。此外,它还具有区分两个包的能力

. > Compare To

此外,还有一个具有额外功能的+版本可用。但免费版也为逆戟鲸提供了一个很好的选择。

在部署安装包之前,我总是控制它的内容。

这只是一个简单的命令行调用(根据terrence的帖子),打开命令行并进入

msiexec /a Package.msi /qb TARGETDIR="%CD%\Extract" /l*vx "%CD\install.log%"

这将把包内容提取到带有当前路径的子目录“extract”中。

设置DISABLEADVTSHORTCUTS属性以强制安装程序中所有发布的快捷方式成为常规快捷方式,并且您不需要包括一个虚拟的reg键来用作键盘。

<Property Id="DISABLEADVTSHORTCUTS" Value="1"/>

我认为Windows Installer 4.0或更高版本是要求

创建一个具有自定义操作的UI,该操作将设置一个变量,并且UI将根据自定义操作中设置的变量禁用/启用下一个按钮(或类似的)。

不像你想的那么简单,也不是太难,只是没有任何文档!

Wix与条件的交互作用,属性&自定义动作< / >

http://trycatchfail.com/blog/post/WiX-Snippet-change-enable32BitAppOnWin64.aspx .设置IIS enable32BitAppOnWin64标志

<InstallExecuteSequence>
<RemoveExistingProducts After="InstallFinalize" />
<Custom Action="ConfigureAppPool" After="InstallFinalize" >
<![CDATA[NOT Installed AND VersionNT64 >= 600]]>
</Custom>
</InstallExecuteSequence>


<CustomAction Id="ConfigureAppPool" Return="check" Directory="TARGETDIR" ExeCommand="[SystemFolder]inetsrv\appcmd set apppool /apppool.name:[APPPOOLNAME] /enable32BitAppOnWin64:false" />

为COM互操作注册。net程序集,兼容x86/x64

注意:这个片段本质上与REGASM Assembly.dll /codebase相同

在这个示例中发生了一些事情,所以这里是代码,我将在后面解释它……

  <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include $(sys.CURRENTDIR)\Config.wxi?>
<?if $(var.Win64) ?>
<?define CLSIDRoots = "CLSID;Wow6432Node\CLSID"?>
<?else ?>
<?define CLSIDRoots = "CLSID"?>
<?endif?>
<!-- ASCOM Driver Assembly with related COM registrations -->
<Fragment>
<DirectoryRef Id="INSTALLLOCATION" />
</Fragment>
<Fragment>
<ComponentGroup Id="cgAscomDriver">
<Component Id="cmpAscomDriver" Directory="INSTALLLOCATION" Guid="{0267031F-991D-4D88-A748-00EC6604171E}">
<File Id="filDriverAssembly" Source="$(var.TiGra.Astronomy.AWRDriveSystem.TargetPath)" KeyPath="yes" Vital="yes" Assembly=".net" AssemblyApplication="filDriverAssembly"  />
<RegistryKey Root="HKCR" Key="$(var.DriverId)"  Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverTypeName)"/>
<RegistryKey Key="CLSID">
<RegistryValue Type="string" Value="$(var.DriverGuid)" />
</RegistryKey>
</RegistryKey>
<?foreach CLSID in $(var.CLSIDRoots) ?>
<RegistryKey Root="HKCR" Key="$(var.CLSID)" Action="none">
<RegistryKey Key="$(var.DriverGuid)" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverTypeName)"/>
<RegistryKey Key="InprocServer32">
<RegistryValue Type="string" Value="mscoree.dll" />
<RegistryValue Type="string" Name="ThreadingModel" Value="Both"/>
<RegistryValue Type="string" Name="Class" Value="$(var.DriverTypeName)"/>
<RegistryValue Type="string" Name="Assembly" Value="!(bind.assemblyFullname.filDriverAssembly)" />
<RegistryValue Type="string" Name="RuntimeVersion" Value="v2.0.50727"/>
<RegistryValue Type="string" Name="CodeBase" Value="file:///[#filDriverAssembly]" />
<RegistryKey Key="!(bind.fileVersion.filDriverAssembly)" >
<RegistryValue Type="string" Name="Class" Value="$(var.DriverTypeName)"/>
<RegistryValue Type="string" Name="Assembly" Value="!(bind.assemblyFullname.filDriverAssembly)" />
<RegistryValue Type="string" Name="RuntimeVersion" Value="v2.0.50727"/>
<RegistryValue Type="string" Name="CodeBase" Value="file:///[#filDriverAssembly]" />
</RegistryKey>
</RegistryKey>
<RegistryKey Key="ProgId" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverId)" />
</RegistryKey>
<RegistryKey Key="Implemented Categories" Action="createAndRemoveOnUninstall" >
<RegistryKey Key="{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Action="createAndRemoveOnUninstall" />
</RegistryKey>
</RegistryKey>
</RegistryKey>
<?endforeach?>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

如果你想知道,这实际上是为ASCOM望远镜驱动程序

首先,我采纳了上面的建议,在一个单独的文件中创建了一些平台变量,你可以在XML中看到这些变量。

接近顶部的if-then-else部分处理x86 vs x64兼容性。我的程序集目标是“任何CPU”,所以在x64系统上,我需要注册它两次,一次在64位注册表中,一次在32位Wow6432Node区域中。if-then-else为我设置了这个,这些值将在后面的foreach循环中使用。这样,我只需要编写一次注册表项(DRY原则)。

file元素指定实际安装和注册的程序集dll:

<File Id="filDriverAssembly" Source="$(var.TiGra.Astronomy.AWRDriveSystem.TargetPath)" KeyPath="yes" Vital="yes" Assembly=".net" AssemblyApplication="filDriverAssembly"  />

没什么革命性的,但注意Assembly=".net" -这个属性本身会导致程序集被放入GAC,这不是我想要的。使用AssemblyApplication属性指向自身只是阻止Wix将文件放入GAC的一种方法。现在Wix知道它是一个。net程序集,但是,它允许我在XML中使用某些绑定器变量,例如!(bind.assemblyFullname.filDriverAssembly)来获得程序集的全名。