WiX技巧和技巧

我们已经使用了一段时间的WiX,尽pipe通常的使用方便的抱怨,它是相当不错的。 我正在寻找的是有用的build议:

  • 设置一个WiX项目(布局,引用,文件模式)
  • 将WiX整合到解决scheme中,并构build/发布stream程
  • configuration安装程序以进行新的安装和升级
  • 任何好的WiX黑客你想分享
  1. 将variables保存在一个独立的wxi包含文件中。 启用重用,variables更快find和(如果需要)允许更容易操作的外部工具。

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

     <!-- 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. 将安装位置存储在registry中,使升级能够find正确的位置。 例如,如果用户设置自定义安装目录。

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

    注意 :WiX大师Rob Mensching发布了一个非常好的博客文章 ,详细介绍了从命令行设置属性时的边界情况。

    使用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. 最简单的方法是进行重大升级 ,因为它允许在单个MSI中进行新的安装和升级。 升级代码固定到一个独特的Guid,永远不会改变,除非我们不想升级现有的产品。

    注意 :在WiX 3.5中有一个新的重要升级元素,使生活更加轻松 !

  5. 在“添加/删除程序”中创build一个图标

     <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. 在发布版本上,我们版本安装程序,将msi文件复制到部署目录。 一个使用从AfterBuild目标调用的wixproj目标的例子:

     <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. 使用热量通过通配符(*)Guid收集文件。 如果您想要在多个项目中重复使用WXS文件,请参阅相关产品的多个版本。 例如,这个batch file自动收获RoboHelp输出。

     @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用于创build一个variables来指定源目录(Web部署输出)。

  8. 通过使用Strings.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. 节省一些痛苦,并遵循Wim Coehen对每个文件一个组件的build议 。 这也允许你省略(或通配符* ) 组件的GUID 。

  10. Rob Mensching有一个很好的方法来通过searchvalue 3来快速追踪MSI日志文件中的问题。 请注意有关国际化的意见。

  11. 添加条件function时,将默认function级别设置为0(禁用)更为直观,然后将条件级别设置为所需的值。 如果将默认function级别设置为> = 1,则条件级别必须为0才能将其禁用,这意味着条件逻辑必须与您所期望的相反,这可能会造成混淆:)

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

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

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

将所有ID保存在不同的名称空间中

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

我发现这有助于在所有不同类别的所有的各种身份证的跟踪非常有帮助。

神奇的问题。 我很乐意看到一些最佳实践。

我有很多我分发的文件,所以我把我的项目设置成了几个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文件的其余部分由包含在Product.wxs的Feature标记中引用的ComponentGroups的碎片组成。 我的项目包含我分发的文件的一个很好的逻辑分组

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

这不是完美的,我的面向对象的蜘蛛感觉刺痛了一点,因为片段必须在Product.wxs文件中引用名字(例如DirectoryRef),但是我发现维护一个大的源文件更容易。

我很乐意听到这个评论,或者如果有人有任何好的提示呢!

添加一个checkbox到退出对话框来启动应用程序或帮助文件。

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

如果你这样做,那么“标准”的外观就不太对。 checkbox总是灰色的背景,而对话框是白色的:

替代文字http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3http://img.dovov.comexit_dlg_1.gif

解决这个问题的方法之一就是指定你自己定制的ExitDialog,并设置一个不同的checkbox 。 这工作,但似乎只是改变一个控件的颜色很多工作。 解决同样事情的另一种方法是对生成的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(); 

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

替代文字http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3http://img.dovov.comexit_dlg_2.gif

使用相同的源文件创buildLive,Test,Training,…版本。

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

先决条件

  • MSBuild社区任务

假设

  • WiXvariables用于定义UpgradeCode,ProductName,InstallName。
  • 您已经有一个工作安装程序。 我不会尝试这个,直到你做。
  • 您的所有组件都保存在一个文件(Components.wxs)中。 如果你有多个文件,这个过程将会起作用,只会有更多的工作要做。

目录结构

  • Setup.Library
    • 所有wxs文件(组件,function,UI对话框…)
    • Common.Config.wxi(ProductCode =“*”,ProductVersion,PlatformProgramFilesFolder,…)
  • Setup.Live (wixproj)
    • 使用“添加现有文件” – >“添加为链接”(Visual Studio中添加button旁边的向下箭头button)链接所有Setup.Library文件
    • Config.wxi(具有唯一的UpgradeCode,ProductName,InstallName,…)
  • Setup.Test ,…
    • 按照活动,但Config.wxi被configuration为testing环境。

处理

  • 创buildSetup.Library目录,并从现有项目中移动所有wx和wxi文件(Config.wxi除外)。
  • 按照正常的wixproj创buildSetup.Live,Setup.Test等。
  • 在Setup.Live等wixproj中添加BeforeBuild目标来执行MSBuild社区任务FileUpdate来修改GUID(我使用A为Live,B为testing,C为训练)
  • 添加AfterBuild目标以将Components.wxs Guid恢复为0。
  • 使用Orcavalidation每个MSI中的每个组件都具有修改后的GUID。
  • validation原始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"> ... 

注意:现在我build议把Guid属性留给Component(相当于* ),每个组件使用一个文件并将文件设置为keypath。 这消除了调用下面显示的ModifyComponentsGuidsRevertComponentsGuids目标的需要。 尽pipe这可能不适用于所有组件。

示例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;([af]|[AF]|\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;([af]|[AF]|\d)" ReplacementText="Guid=&quot;0" /> </Target> 

最后的想法

  • 此过程也适用于为同一安装程序为不同的合并模块(Live,Test,…作为特征)创build不同的安装程序。 我使用了不同的安装程序,因为这似乎是一个更安全的选项,如果有人可能在同一个盒子上,而且只是使用不同的合并模块的function,那么有人可能升级Live而不是培训。
  • 如果您使用MSI执行升级以及新安装,即仅主要升级方法,并且将您的安装位置保存在registry中,请记住为每个安装的密钥名称创build一个variables。
  • 我们还在每个Config.wxi中创buildvariables,为每个安装程序启用唯一的虚拟目录名称,应用程序池,数据库名称等等。

更新1:如果您为每个文件创build带有Guid =“*”的组件,则将自动生成组件Guids删除调用FileUpdate任务的需要,将该文件设置为关键path。

更新2:我们遇到的问题之一是,如果你不自动生成组件Guid的和构build失败,那么临时文件需要手动删除。

更新3:find一种方法来消除对svn:externals和临时文件创build的依赖。 这使得构build过程更有弹性(如果不能通配你的Guids,则是最好的select),如果在光照或蜡烛中有构build失败,则构build过程会变得更脆弱。

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

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

msiexec /i Package.msi /l*vc:\Package.log

哪里

  Package.msi 

是你的包的名字和

  C:\ Package.log 

是你想要输出日志的地方

Msi错误代码

Wix介绍录影
哦,随机WIX介绍video“先生WiX”罗布Mensching是“概念大图”有帮助。

使用Javascript CustomActions,因为它们很容易

人们曾经说过, Javascript是MSI CustomActions使用的错误 。 给出的原因:难以debugging,难以使其可靠。 我不同意。 debugging并不难,肯定比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查询,以获得一个可以安装ISAPIfilter的现有网站列表。 这个列表然后用来填充稍后在UI序列中显示的列表框。 一切都很简单。

在IIS7上,没有IIS的WMI提供程序,所以我使用了shell.Run()方法来调用appcmd.exe来执行这个工作。 简单。

相关问题: 关于Javascript CustomActions

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”,因为它是应用程序的直接依赖关系,即使应用程序作者从未意识到它是“B”的依赖关系,也会自动拉入组件组“A”。 只要你没有任何循环依赖,它就“正常工作”。

可重复使用的wixlib

如果使用lit.exe将大型池可重用组件编译为可重复使用的wixlib,上述依赖关系图想法效果最佳。 创build应用程序设置时,可以像wixobj文件一样引用此wixlib。 candle.exe链接器将自动消除任何未被主要产品wxs文件“拉入”的片段。

我很惊讶没有人提到使用T4在生成过程中生成WXS文件。 我通过亨利·李@ 新时代解决scheme了解到这一点。

本质上,你创build一个自定义的MSBuild任务来执行一个T4模板,该模板在Wix项目编译之前输出WXS。 这允许你(根据实现的方式)自动包含编译另一个解决scheme的所有程序集输出(意味着你不再需要在添加新程序集的时候编辑wxs)。

使用Heat.exe粉碎脸部,并在痛苦的大安装造成“史诗Pwnage”

在Si和Robert-P关于热量的回答上扩大了。

翻译:(使用加热避免手动input单个文件到项目中,并自动生成一个整体更容易的过程。)

WiX 2.0热量语法详细

对于较新的版本(并非所有与旧版本不同,但有可能令人讨厌的语法更改….)进入目录Heat从cmd.exe进来,只需input热,但我有一个例子在这里帮助如果需要更新的版本。

将以下内容添加到Visual Studio 2010中的生成事件中。
(右键点击Project-> Properties – > Build Events-> Pre-Build Events)

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

  -GG 

当运行加热时产生Guids(就像在执行上面的命令时一样)

  -scom 

不要抢“COM文件”

  -sreg 

不要抢“registry文件”

  -sfrag 

不要抢“碎片”

  -srd 

不要抢“根目录”

  DIR 

dir表示你想让Heat在一个文件夹中查找

  “$(EnviromentVariable)” 

在(右键单击项目,转到属性)项目属性 – >构build部分中的预处理器variables中添加预处理器variables的variables的名称(假定为Visual Studio 2010)

 例:
 EnviromentVariable = C:\项目\ BIN \debugging; 

没有双引号,但以分号结尾

  -cg GroupVariable 

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

  FragmentDir 

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

  FileName.wxs 

文件的名称

在这里完整的教程,所以freakin有帮助

第1 部分第2部分

包括COM对象:

heat生成所有大部分(如果不是全部的话)registry项和其他所需的configuration。 麾!

包括pipe理的COM对象(又名,.NET或C#COM对象)

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

如果你不需要GAC中的库(即全局可用:大多数时候你不需要这个.NET程序集,那么你现在可能做了一些错误的事情,如果它不打算成为一个共享库),您将需要确保将CodeBaseregistry项更新为[#ComponentName] 。 如果您正在计划将其安装到GAC(例如,您已经创build了一个所有人都希望使用的新的令人敬畏的通用库),则必须删除此条目,并将两个新属性添加到File元素: AssemblyKeyPath 。 程序集应该设置为“.net”, KeyPath应该设置为“yes”。

但是,某些环境(特别是任何具有托pipe内存的脚本语言)也需要访问Typelib。 确保在你的typelib上运行并包含它。 heat将生成所有需要的registry项。 多么酷啊?

安装到C:\ProductName

一些应用程序需要安装到C:\ProductName或类似的东西,但是网上的例子中有99.9%(如果不是100%)安装到C:\Program Files\CompanyName\ProductName

以下代码可用于将TARGETDIR属性设置为C:驱动器的根目录(取自WiX用户列表 ):

 <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:\ ! 它相当指向ROOTDRIVEROOTDRIVE又指向具有最大可用空间的驱动器根目录 ( 请参见此处 ) – 而这不一定是C:驱动器。 有可能是另一个硬盘驱动器,分区或USB驱动器!

然后,在<Product ...>标签下面的某处,像往常一样需要以下目录标签:

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

Environmental Variables

When compiling your Wxs documents to wixobj code, you can make use of environmental variables to determine various information. For example, lets say you want to change which files get included in a project. Lets say you have an environmental variable called RELEASE_MODE, that you set right before you build your MSI (either with a script or manually, it doesn't matter) In your wix source, you can do something like:

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

and then later in your code, use it in place to on the fly change your wxs document, eg:

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

Creating Custom Action for WIX written in managed code (C#) without Votive

http://www.codeproject.com/KB/install/wixcustomaction.aspx

Editing Dialogs

One good ability to edit dialogs is using SharpDevelop in a version 4.0.1.7090 (or higher). With help of this tool a standalone dialog (wxs files from WiX sources like eg InstallDirDlg.wxs) can be opened, previewed and edited in Design view.

Setting the IIS enable32BitAppOnWin64 flag http://trycatchfail.com/blog/post/WiX-Snippet-change-enable32BitAppOnWin64.aspx

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

Modify the "Ready to install?" dialog (aka VerifyReadyDlg) to provide a summary of choices made.

它看起来像这样:
alt text 160wpb4.jpg

Do this with a 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); } 

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

Attach the CA to a button. In this example, the CA is fired when Next is clicked from the CustomizeDlg:

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

Related SO Question: How can I set, at runtime, the text to be displayed in VerifyReadyDlg?

Put Components which may be patched individually inside their own Fragments

It goes for both making product installers and patches that if you include any component in a fragment, you must include all of the components in that fragment. In the case of building an installer, if you miss any component references, you'll get a linking error from light.exe. However, when you make a patch, if you include a single component reference in a fragment, then all changed components from that fragment will show up in your patch.

喜欢这个:

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

Also, when patching using the "Using Purely WiX" topic from the WiX.chm help file, using this procedure to generate the patch:

 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 

it's not enough to just have the 1.1 version of the product.wixpdb built using the components in separate fragments. So be sure to correctly fragment your product before shipping.

Printing EULA from Wix3.0 and later

1) When you compile your wix source code, the light.exe must reference the WixUIExtension.dll in command line. Use the command line switch -ext for this.

2) If when you add the reference to the WixUIExtension.dll, your project fails to compile, this is most likely because of clashes of Dialog IDs, ie your project was using the same IDs of dialogs as some standard dialogs in WixUIExtension.dll, give different IDs to your dialogs. This is quite common problem.

3) Your license dialog must have ScrollableText control with the id "LicenseText". Wix searches for exactly this name of control when it prints.

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

and a PushButton which refers to the custom action

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

4) Define CustomAction with the Id="PrintEula" like this:

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

Note: BinaryKey is different in Wix3.0 comparing to Wix2.0 and must be exactly "WixUIWixca" (case sensitive).

When user presses the button he/she will be presented with the standard Select Printer Dialog and will be able to print from there.

  • We display the product version somewhere (tiny) in the first screen of the GUI. Because people tend to make mistakes in picking the right version every time. (And keep us developers searching for ages..)

  • We've set up TFSBuild to also generate transforms (.mst files) with the configuration for our different environments. (We know about all environments we need to deploy to).

Since the original weblog post by Grant Holliday is down, I copy pasted its contents here:


MSBuild task to generate MSI Transform files from XMLMarch 11 2008

In my previous post I described how you can use MSI Transform (*.mst) files to separate environment-specific configuration settings from a generic MSI package.

Although this provides a level of flexibility in your configuration, there are two down-sides of Transform files:

  1. They're a binary format
  2. You can't “edit” or “view” a transform file. You have to apply it or re-create it to see what changes it includes.

Fortunately we can use the Microsoft Windows Installer Object Library (c:windowssystem32msi.dll) to open MSI “databases” and create transform files.

Credits go again to Alex Shevchuk – From MSI to WiX – Part 7 – Customising installation using Transforms for showing us how to achieve this with VbScript. Essentially all I've done is taken Alex's example and using Interop.WindowsInstaller.dll I've implemented an MSBuild task. The MSBuild Task

Download the source code & example transforms.xml here (~7Kb Zipped VS2008 Solution)


Before deploying an install package I always control the content of it.

It's just a simple call at the command line (according to Terrences post) open command line and enter

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

This will extract package contents to an subdir 'Extract' with the current path.

Instead of ORCA use InstEd which is a good tool for viewing MSI tables. Also it has the ability to diff two packages by Transform -> Compare To…

Additionally a Plus version with additional functionality is available. But also the free version offers a good alternative for Orca.

Registering .NET assemblies for COM Interop with x86/x64 compatibility

NB This fragment is essentially the same as REGASM Assembly.dll /codebase

A couple of things are going on in this sample so here's the code and I'll explain it afterwards…

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

If you were wondering, this is actually for an ASCOM Telescope Driver .

First, I took advice from above and created some platforma variables in a seperate file, you can see those scattered through the XML.

The if-then-else part near the top deals with x86 vs x64 compatibility. My assembly targets 'Any CPU' so on an x64 system, I need to register it twice, once in the 64-bit registry and once in the 32-bit Wow6432Node areas. The if-then-else sets me up for this, the values are used in a foreach loop later on. This way, I only have to author the registry keys once (DRY principle).

The file element specifies the actual assembly dll being installed and registered:

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

Nothing revolutionary, but notice the Assembly=".net" – this attribute alone would cause the assembly to be put into the GAC, which is NOT what I wanted. Using the AssemblyApplication attribute to point back to itself is simply a way of stopping Wix putting the file into the GAC. Now that Wix knows it's a .net assembly, though, it lets me use certain binder variables within my XML, such as the !(bind.assemblyFullname.filDriverAssembly) to get the assembly full name.

Set the DISABLEADVTSHORTCUTS property to force all advertised shortcuts in your installer to become regular shortcuts, and you don't need to include a dummy reg key to be used as the keypath.

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

I think Windows Installer 4.0 or higher is a requirement .

It's a nice structure but based on my experience I wonder how you address these conditions:

A. Your installs all appear to land in the same destination. If a user needs to install all 3 versions at once will your process allow this. Can they unambiguously tell which version of every executable they are triggering?

B. How do you handle new files that exist in TEST and/or TRAINING but not yet in LIVE?

Here's a way to help large web projects verify that the number of deployed files matches the number of files built into an MSI (or merge module). I've just run the custom MSBuild task against our main application (still in development) and it picked up quite a few missing files, mostly images, but a few javascript files had slipped through to!

This approach (peeking into File table of MSI by hooking into AfterBuild target of WiX project) could work for other application types where you have access to a complete list of expected files.

Performing a forced reinstall when an install doesn't allow uninstall or reinstall and doesn't roll back.

VBscript script used for overriding an install that isn't uninstalling for whatever reason..

 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) 

Create a UI that has a custom action that will set a variable and the UI will disable/enable the next button (or similar) based upon the variable set in the custom action.

Not as straight-forward as you would think, not too difficult just not documented anywhere!

Wix Interactions with Conditions, Properties & Custom Actions