WiX 技巧和窍门

Posted

技术标签:

【中文标题】WiX 技巧和窍门【英文标题】:WiX tricks and tips 【发布时间】:2010-10-03 01:17:50 【问题描述】:

我们使用 WiX 已经有一段时间了,尽管人们通常抱怨易用性,但它运行得相当不错。我正在寻找的是关于以下方面的有用建议:

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

【问题讨论】:

看看 gui4wix.codeplex.com 因不具建设性而关闭?我从问这个问题中学到了很多东西! *** 的一点一致性也会很好......例如***.com/questions/550632/… 它得到了 '203' Ups,这足以证明它的有用性。 SO 问题必须有明确、正确的答案;开放式问题使人们提出的有关实际问题的问题从首页上消失。 faq @Si.: AFAIK 一直有该政策,但现在执行得更好;这个问题已经问了***年了。 公平的吉姆,这是一个开放式问题,我想这取决于 SO 社区来决定,但我不得不说,鉴于我和从外观上看,许多其他人发现这个问题很有用(例如 goo.gl/Zqp2X),并且非常适合常见问题解答中的 practical, answerable questions based on actual problems that you face 部分。 【参考方案1】:

    将变量保存在单独的 wxi 包含文件中。可以重复使用,可以更快地找到变量,并且(如果需要)可以更轻松地通过外部工具进行操作。

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

    将安装位置存储在注册表中,使升级能够找到正确的位置。例如,如果用户设置自定义安装目录。

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

    注意:WiX 大师 Rob Mensching 已发布了一个 excellent blog entry,其中更详细地介绍并修复了从命令行设置属性时的边缘情况。

    使用 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)">
    

    最简单的方法总是使用major upgrades,因为它允许在单个 MSI 中进行新安装和升级。 UpgradeCode 固定为唯一的 Guid,并且永远不会改变,除非我们不想升级现有产品。

    注意:在 WiX 3.5 中,有一个新的 MajorUpgrade 元素,它使生活 even easier!

    在添加/删除程序中创建图标

    <Icon Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" />
    <Property Id="ARPPRODUCTICON" Value="Company.ico" />
    <Property Id="ARPHELPLINK" Value="http://www.example.com/" />
    

    在发布版本时,我们对安装程序进行版本控制,将 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>
    

    使用 heat 收集带有通配符 (*) Guid 的文件。如果您想跨多个项目重用 WXS 文件,这很有用(请参阅我对同一产品的多个版本的回答)。例如,此批处理文件会自动收集 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 用于创建一个变量来指定源目录(web部署输出)。

    通过使用 Strings.wxl 进行本地化,可以轻松地将产品版本包含在欢迎对话框标题中。 (来源:saschabeaumont。添加这个很棒的提示隐藏在评论中)

    <WixLocalization Culture="en-US" xmlns="http://schemas.microsoft.com/wix/2006/localization">
        <String Id="WelcomeDlgTitle">\WixUI_Font_BiggerWelcome to the [ProductName] [ProductVersion] Setup Wizard</String>
    </WixLocalization>
    

    免去一些痛苦,并按照每个文件一个组件的Wim Coehen's advice。这也允许您省略(或通配符 *)component GUID。

    Rob Mensching 有一个 neat way,可通过搜索 value 3 快速跟踪 MSI 日志文件中的问题。请注意有关国际化的 cmets。

    添加条件功能时,将默认功能级别设置为 0(禁用)然后将条件级别设置为您想要的值会更直观。如果您将默认功能级别设置为 >= 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>
    

【讨论】:

关于在添加/删除程序中添加图标,这正是我想要的。你把这三行放在哪里? +1 绝对令人敬畏。 我倾向于将它们放在 元素之后(显然是下方)。查看架构的有效性wix.sourceforge.net/manual-wix3/schema_index.htm +1,希望我能做到 +100,这是我偶然发现的最有用的 Wix 信息。 谢谢蒂姆! Rob Mensching、Bob Arson、Wim Coehen 和其他人分享他们的知识值得称赞。【参考方案2】:

检查是否安装了 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 Compatibility:

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

【讨论】:

【参考方案3】:

将所有 ID 保存在单独的命名空间中

功能以F. 开头示例:F.Documentation、F.Binaries、F.SampleCode。 组件以 C. 开头 例如:C.ChmFile、C.ReleaseNotes、C.LicenseFile、C.IniFile、C.Registry CustomAction 为 CA. 例如:CA.LaunchHelp、CA.UpdateReadyDlg、CA.SetPropertyX 文件是Fi. 目录为Di. 等等。

我发现这极大地有助于跟踪所有不同类别中的所有不同 ID。

【讨论】:

我不使用命名空间,但我附加了 ID;例如:ExamplesFeature、ChmFileComponent。我想我喜欢打字 ;-)【参考方案4】:

好问题。我很想看到一些最佳实践。

我有很多要分发的文件,所以我将我的项目设置为几个 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>

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

我很想听听 cmets 的意见,或者如果有人也有任何好的建议!

【讨论】:

我们的设置也与这种方法非常相似。这很好,因为我们可以使用等效的 Products.wxs 作为各种产品的基础设置。 @Peter Tate:你的蜘蛛感觉是正确的。请参阅我关于目录别名的答案。 我采用相同的方法:Product.wxs 布局是静态的,构建任务(heat.exe)生成我的 Content.wxs 文件【参考方案5】:

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

...

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

解决此问题的一种方法是发送至specify your own custom ExitDialog, with a differently-located checkbox。这可行,但似乎只是为了更改一个控件的颜色需要做很多工作。解决同一问题的另一种方法是对生成的 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

【讨论】:

哈!不是我的博客。我也读过。我在上面的文字中有一个指向博客条目的链接。但他们的做法与我不同。我更喜欢我的方式。!! 感谢js,很有帮助!我必须在 wxs 中更改的一件事是将 WIXUI_EXITDIALOGOPTIONALCHECKBOX 替换为 WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed&lt;Publish&gt; 有没有办法让复选框默认选中? 默认选中该框,我使用了这个: 似乎是一个不错的解决方案,但我该如何使用它呢?有什么方法可以将 js 放入我的 wixproj 的 元素中?或者既然你指的是从命令行运行它,那么作为构建后事件是否更好,在这种情况下,什么是适用于 Windows 的好的 js 命令行解释器?【参考方案6】:

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

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

先决条件

MSBuild Community Tasks

假设

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,... 根据实际情况,但 Config.wxi 配置为测试环境。

流程

创建 Setup.Library 目录并从现有项目中移动所有 wxs 和 wxi 文件(Config.wxi 除外)。 按照普通的 wixproj 创建 Setup.Live、Setup.Test 等。 在 Setup.Live 等中的 wixproj 中添加 BeforeBuild 目标以执行 MSBuild 社区任务 FileUpdate 以修改 Guid(我使用 A 表示 Live,B 表示测试,C 表示训练) 添加 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>

Example 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 之外(相当于 *),每个组件使用一个文件并将该文件设置为 keypath。这消除了调用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 而不是 Training,并且您只是使用不同合并模块的功能,则存在更大的风险。 如果您使用 MSI 执行升级和新安装(即仅进行主要升级的方法),并且将安装位置保存在注册表中,请记住为每次安装的密钥名称创建一个变量。 我们还在每个 Config.wxi 中创建变量,以便为每个安装程序启用唯一的虚拟目录名称、应用程序池、数据库名称等。

更新 1:Auto-generating component Guids 消除了调用 FileUpdate 任务的需要,如果您为每个文件创建带有 Guid="*" 的组件,并将文件设置为密钥路径。

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

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

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

【讨论】:

+1 用于 MSBuild 社区任务参考,喜欢这个包【参考方案7】:

使用 Msi 诊断日志获取详细的故障信息 msiexec /i Package.msi /l*v c:\Package.log

在哪里

Package.msi
是你的包的名字
c:\Package.log
是您想要输出日志的位置

Msi Error Codes

Wix 介绍视频Oh and Random Wix intro video featuring "Mr. WiX" Rob Mensching is "conceptual big picture" helpful.

【讨论】:

+1 如果我们可以从 Wix 内部而不是命令行启用日志记录会更好。 WiX 可以。设置 MsiLogging 属性。仅受 Windows Installer 4.0+ 支持。 非常感谢“Wix 先生”。必须检查一下。【参考方案8】:

使用 Javascript 自定义操作,因为它们非常简单

人们说Javascript is the wrong thing to use for MSI CustomActions。给出的原因:难以调试,难以使其可靠。我不同意。调试并不难,当然不比 C++ 难。它只是不同。我发现用 Javascript 编写 CustomAction 超级简单,比使用 C++ 容易得多。快多了。同样可靠。

只有一个缺点:Javascript CustomAction 可以通过 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 来执行工作。很简单。

相关问题:About Javascript CustomActions

【讨论】:

+1 我发现 DTF 方法很容易设置,但 javascript 也很有用。【参考方案9】:

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 将 big-pool-o-reusable-components 编译成可重用的 wixlib,则上述依赖图的想法效果最佳。创建应用程序设置时,您可以像 wixobj 文件一样引用此 wixlib。 Candle.exe 链接器将自动消除任何未被主产品 wxs 文件“拉入”的片段。

【讨论】:

【参考方案10】:

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

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

【讨论】:

+1 非常好,我不太担心程序集,但是我们的 Web 项目可能会遇到添加到项目中但没有的 aspx 页面和其他工件(图像、css)的问题维克斯。 对于未来的访问者,Wix 3.5 有一个实用程序 heat.exe 可以自动执行此收获 @Mrchief - 我不相信 Heat 会选择在本地复制的引用程序集 - 不过这显然是 4.0 计划的。参考:sourceforge.net/tracker/… Heat 不会拾取引用的程序集。 有哪些使用T4生成WXS文件的好例子?【参考方案11】:

使用 Heat.exe 砸脸并在痛苦的大型安装中施加“史诗般的 Pwnage”

扩展 Si's 和 Robert-P's 回答关于热量的问题。 翻译: (使用热量来避免手动将单个文件输入到项目中,并自动构建以使整个过程更容易。) WiX 2.0 Heat Syntax detailed

对于较新的版本(与旧版本没有什么不同,但可能存在令人讨厌的语法更改......)从 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 

在热运行时生成 Guid(如执行上述命令时)

-scom 

不要抓取“COM 文件”

-sreg 

不要抢“注册表文件”

-sfrag 

不要抓“碎片”

-srd 

不要抢“根目录”

目录

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

"$(EnviromentVariable)"

您将添加到(右键单击项目,转到属性)项目属性中的预处理器变量的变量的名称->构建部分,其中显示定义预处理器变量(假设为 Visual Studio 2010)

示例:
EnviromentVariable=C:\Project\bin\Debug;
无双引号但以分号结尾
-cg 组变量

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

片段目录

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

文件名.wxs

文件名

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

Part 1 Part 2

【讨论】:

还有另一个用于稍微不同目的的有用工具:Paraffin (wintellect.com/CS/blogs/jrobbins/archive/2010/03/10/4107.aspx)【参考方案12】:

包括 COM 对象:

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

包括托管 COM 对象(也称为 .NET 或 C# COM 对象)

在托管 COM 对象上使用 heat 将为您提供几乎完整的 wix 文档。

如果您不需要 GAC 中可用的库(即全局可用:大多数情况下,您的 .NET 程序集都不需要此库 - 如果不是,您可能在这一点上做错了打算成为一个共享库)您需要确保将CodeBase 注册表项更新为[#ComponentName]。如果您计划将其安装到 GAC(例如,您创建了一些人人都想使用的新的很棒的公共库),您必须删除此条目,并向 File 元素添加两个新属性:AssemblyKeyPath。程序集应设置为“.net”,KeyPath 应设置为“yes”。

但是,某些环境(尤其是任何具有托管内存的环境,例如脚本语言)也需要访问 Typelib。 Make sure to run heat on your typelib 并包含它。 heat 将生成所有需要的注册表项。这有多酷?

【讨论】:

【参考方案13】:

安装到C:\ProductName

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

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

<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,而后者又指向具有最多可用空间的驱动器的根目录 (see here) - 这不一定是C: 驱动器。可能有另一个硬盘驱动器、分区或 USB 驱动器!

然后,在您的&lt;Product ...&gt; 标签下方的某个位置,您照常需要以下目录标签:

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

【讨论】:

安装到WindowsVolume不是更简单吗? 是的,但您必须使用解决方法,因为 WindowsVolume 属性可能不能用作 Directory(编译器给出错误/警告),正如 here 和 @ 指出的那样987654324@。就个人而言,我发现这种解决方法令人困惑。【参考方案14】:

环境变量

在将你的Wxs 文件编译成wixobj 代码时,你可以利用环境变量来确定各种信息。例如,假设您想更改项目中包含的文件。假设您有一个名为 RELEASE_MODE 的环境变量,您在构建 MSI 之前设置了它(使用脚本或手动,没关系)在您的 wix 源中,您可以执行以下操作:

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

然后在您的代码中,使用它来即时更改您的 wxs 文档,例如:

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

【讨论】:

还可以使用 $(Configuration) 和 $(Platform) 等编译变量。还有更多msdn.microsoft.com/en-us/library/aa302186.aspx @Si - 在今天之前的某个时间,该链接不再有效。我找不到最新的。【参考方案15】:

使用 RobM 特殊的 “记住属性” 模式

http://robmensching.com/blog/posts/2010/5/2/The-WiX-toolsets-Remember-Property-pattern

【讨论】:

【参考方案16】:

为使用托管代码 (C#) 编写的 WIX 创建自定义操作,无需 Votive

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

【讨论】:

【参考方案17】:

编辑对话框

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

【讨论】:

太棒了,不知道 SharpDevelop 支持这个。【参考方案18】:

设置 IIS enable32BitAppOnWin64 标志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" />

【讨论】:

【参考方案19】:

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

看起来像这样:alt text http://i46.tinypic.com/s4th7t.jpg

使用 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问题:How can I set, at runtime, the text to be displayed in VerifyReadyDlg?

【讨论】:

这当然不应该是 Windows 脚本语言的 JScript 而不是 Dhtml 脚本语言的 JavaScript。可能有点迂腐,但可能会让某些人感到困惑。【参考方案20】:

将可以单独修补的组件放入自己的片段中

它适用于制作产品安装程序和补丁程序,如果您在片段中包含任何组件,则必须在该片段中包含所有组件。在构建安装程序的情况下,如果您错过任何组件引用,您将收到来自 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.chm 帮助文件中的“使用 Purely WiX”主题进行修补时,使用以下过程生成修补程序:

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 版本的 product.wixpdb 是不够的。因此,请务必在发货前正确分割您的产品。

【讨论】:

【参考方案21】:

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

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

2) 如果当您添加对 WixUIExtension.dll 的引用时,您的项目无法编译,这很可能是因为对话框 ID 冲突,即您的项目使用的对话框 ID 与 WixUIExtension 中的某些标准对话框相同.dll,为您的对话框提供不同的 ID。这是很常见的问题。

3) 您的许可证对话框必须有 ScrollableText 控件,其 id 为“LicenseText”。 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" />

注意:与 Wix2.0 相比,Wix3.0 中的 BinaryKey 不同,必须完全是“WixUIWixca”(区分大小写)。

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

【讨论】:

【参考方案22】:

我们在 GUI 的第一个屏幕的某处(很小)显示产品版本。因为人们在每次选择正确的版本时都会犯错误。 (并让我们的开发人员搜索年龄..)

我们已设置 TFSBuild 以生成转换(.mst 文件),其中包含我们不同环境的配置。 (我们知道我们需要部署到的所有环境)。

由于格兰特霍利迪的原始博客文章已下架,我将其内容复制粘贴在这里:


从 XMLMarch 11 2008 生成 MSI 转换文件的 MSBuild 任务

在我之前的帖子中,我描述了如何使用 MSI 转换 (*.mst) 文件将特定于环境的配置设置与通用 MSI 包分开。

虽然这为您的配置提供了一定程度的灵活性,但转换文件有两个缺点:

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

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

再次感谢Alex Shevchuk – From MSI to WiX – Part 7 - 使用转换自定义安装,向我们展示了如何使用 VbScript 实现这一目标。基本上我所做的一切都是以 Alex 的例子,并使用 Interop.WindowsInstaller.dll 实现了一个 MSBuild 任务。 MSBuild 任务

Download 源代码和示例 transforms.xml 在这里(~7Kb 压缩 VS2008 解决方案)


【讨论】:

我们在本地化文件中重新定义了 WelcomeDlgTitle - 效果很好! \WixUI_Font_Bigger欢迎使用 [ProductName] [ProductVersion] 安装向导【参考方案23】:

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

这只是在命令行中的一个简单调用(根据 Terrences 的帖子)打开命令行并输入

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

这会将包内容提取到具有当前路径的子目录“Extract”。

【讨论】:

【参考方案24】:

使用 InstEd 代替 ORCA,这是查看 MSI 表的好工具。它还可以通过以下方式区分两个包 变换 -> 比较...

另外还有一个具有附加功能的Plus version 可用。但免费版本也为 Orca 提供了一个不错的选择。

【讨论】:

【参考方案25】:

为具有 x86/x64 兼容性的 COM 互操作注册 .NET 程序集

注意这个片段与 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 Telescope Driver。

首先,我听取了上面的建议,在一个单独的文件中创建了一些平台变量,你可以看到那些分散在 XML 中的变量。

靠近顶部的 if-then-else 部分处理 x86 与 x64 的兼容性。我的程序集以 x64 系统上的“任何 CPU”为目标,我需要注册两次,一次在 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) 来获取程序集全名。

【讨论】:

【参考方案26】:

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

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

我认为 Windows Installer 4.0 或更高版本是 requirement。

【讨论】:

【参考方案27】:

这是一个不错的结构,但根据我的经验,我想知道您如何解决这些问题:

A.您的安装似乎都落在同一个目的地。如果用户需要一次安装所有 3 个版本,您的流程是否允许这样做。他们能清楚地知道他们触发了每个可执行文件的哪个版本吗?

B.您如何处理存在于 TEST 和/或 TRAINING 但尚未在 LIVE 中的新文件?

【讨论】:

嗨,布莱恩,A。不,他们没有。 InstallName 在 Config.wxi 中,这是 svn:externals 唯一没有引用的文件。因此,对于每个安装,即每个产品,这都是唯一的。这也是我们为每个版本修改 Guid 的原因。 B. GOTO A. :) 它们是独立的 MSI,有自己的 UpgradeCode。 对了,我明白你为什么用一个问题来回答我的问题,但是一旦你获得足够的代表点数,请将你的问题移到答案 cmets,否则线程将难以跟进。跨度> 【参考方案28】:

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

这种方法(通过挂钩到 WiX 项目的 AfterBuild 目标来查看 MSI 的文件表)可能适用于您可以访问完整的预期文件列表的其他应用程序类型。

【讨论】:

【参考方案29】:

当安装不允许卸载或重新安装且不回滚时执行强制重新安装。 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)

【讨论】:

【参考方案30】:

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

不像你想象的那么简单,也不是太难,只是没有记录在任何地方!

Wix Interactions with Conditions, Properties & Custom Actions

【讨论】:

以上是关于WiX 技巧和窍门的主要内容,如果未能解决你的问题,请参考以下文章

text 技巧和窍门

markdown Windows技巧和窍门

sh 卷曲技巧和窍门

5 个 Flutter VSCode 技巧和窍门,你可以马上使用!

Elasticsearch:ingest pipelines - 使用技巧和窍门

MySQL数据库性能优化的技巧和窍门