使用Wix的技巧和提示有哪些?

  • 回答 (10)
  • 关注 (0)
  • 查看 (614)

我想知道在以下方面使用Wix的有效建议:

  • 设置Wix项目(布局、引用、文件模式)
  • 将Wix集成到解决方案中,并构建/发布过程
  • 为新安装和升级配置安装程序
  • 以及你想要分享的关于Wix的优点
liqoeiliqoei提问于
Rom_z全职程序员,喜欢围棋回答于

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

goodbad从精力旺盛的种马过渡到精光内蕴的骏马。回答于

以下技巧是关于在单独的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,上述依赖关系图想法效果最佳。 创建应用程序设置时,可以像wixobj文件一样引用此wixlib。 candle.exe链接器将自动消除任何未被主要产品wxs文件“拉入”的片段。

BlackKnight写一辈子代码,做一辈子好人回答于

Javascript CustomActions

JavaScript用于MSI自定义操作是错误的原因主要是:很难调试,并且不可靠。实际上,调试并不难,只是和C++不一样。在Javascript中编写CustomActions非常容易,比C++容易,快速,并且可靠。

但是有一个缺点:JavaScriptCustomActions可以通过Orca提取,而C/C++CA则需要reverse-engineering。

使用脚本,只需从某种结构开始。

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序列中显示的列表框。

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

关于Javascript CustomActions

文艺青年对穿肠回答于

使用MSI诊断日志获取详细的故障信息

msiexec /i Package.msi /l*v c:\Package.log

Package.msi

package name

c:\Package.log

为日志输出的位置。

MSI错误码

Wix简介视频

wix概念图

少女女先疯队长大学生 码字爱好者 工商管理专业回答于

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

为每个安装程序创建唯一的UpgradeCode,并自动为每个安装程序定义每个Guid的第一个字符,留下剩下的31个惟一字符。

前提条件

假设

  • Wix变量用于定义UpgradeCode、ProductName、InstallName。
  • 安装程序正常工作
  • 所有组件都保存在一个文件中(Components.wxs)。如果你有多个文件,这个过程将会起作用,并且会有更多的工作要做。

目录结构

  • Setup.Library
    • 所有wxs文件(组件、特性、UI对话框)
    • Common.Config.wxi(ProductCode=“*“,ProductVersion,PlatformProgramFilesFold,...)
  • Setup.Live (wixproj)
    • 使用“添加现有文件” - >“添加为链接”(Visual Studio中添加按钮旁边的向下箭头按钮)链接所有Setup.Library文件
    • Config.wxi(有唯一的UpgradeCode,ProductName,InstallName,...)
  • Setup.Test
    • Config.wxi是为Test环境配置的。

过程

  • 创建.Setup.Library目录,并将所有wxs和wxi文件(Config.wxi除外)从现有项目中移出。
  • 按照正常的wixproj.测试环境创建Setup.Live, Setup.Test等程序
  • 在安装过程中,在wixproj中添加BeforeBuild目标,.Live来执行MSBuild社区任务,FileUpdate来修改Guids(我使用了Live, B用于测试,C用于练习)
  • 添加AfterBuild目标,将Components.wxsGuids还原回0。
  • 使用Orca验证每个MSI中的每个组件都有修改的GUID。
  • 验证原始GUID是否已恢复。
  • 确认每个MSI安装(并升级)正确的产品和位置。

Config.Common.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属性排除在组件之外(相当于*),每个组件使用一个文件,并将文件设置为keypath。这消除了调用ModifyComponentsGuids和RevertComponentsGuids目标的需要。但是,对于所有的组件,这应该是不可能的。

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。
  • 如果你使用MSI来执行升级和新安装程序,即主要升级方法,并且将安装位置保存在注册表中,请记住为每个安装创建一个变量名。
  • 我们还在每个Config.wxi中创建变量,为每个安装程序启用唯一的虚拟目录名称、应用程序池、数据库名称等等。

更新1:如果你为每个文件创建带有Guid="*"的组件,则自动生成组件Guids将消除调用FileUpdate任务的需要,将文件设置为keypath。

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

更新3:找到了一种消除对SVN依赖的方法:外部和临时文件创建。这使得构建过程更有弹性(如果不能通配你的Guids,则是最好的选择)。

更新4:在Wix 3.0+中,支持更多使用实例

MH小夜雨时学生回答于

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

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

其中一种方法是指定自定义ExitDialog,并设置一个不同位置的复选框。这是可行的,但似乎只是为了改变一个控件的颜色。解决同样问题的另一种方法是后处理生成的MSI,将控制表中的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.dizzymonkeysign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3/images/exit_dlg_2.gif

i太过热闹的梦回答于

我将项目设置为几个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文件由包含ComponentGroups的片段组成,它们在Product.wxs中的FeatureTag中被引用。我的项目包含了我分发的文件的逻辑分组。

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

片段必须引用Product.wxs文件中的名称(例如DirectoryRef),但我发现维护一个大型源文件更容易。

fengge77回答于

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

  • 特性以F.为例:F.Documment,F.Binary,F.SampleCode。
  • 组件以C.为例:C.ChmFile,C.ReleaseNotes,C.LicenseFile,C.IniFile,C.Registry
  • CustomActions以CA.为例: CA.LaunchHelp, CA.UpdateReadyDlg, CA.SetPropertyX
  • 文件是Fi.
  • 目录是Di.

我发现这对跟踪所有不同类别的各种id非常有帮助。

爱胖次的瓜皮回答于

检查IIS是否已安装:

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

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

查看在Vista+上是否安装了IIS 6 Metabase兼容版本:

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

<Condition Message="IIS 6 Metabase Compatibility feature must be installed">
    Installed OR ((VersionNT &lt; 600) OR IIS_METABASE_COMPAT)
</Condition>
英特奈特见贤思齐回答于
  1. 将变量保存在一个独立的wxi包含文件中。 启用re-use,使得变量更快找到和允许外部工具更容易地进行操作。
  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 ?>

  1. 将安装位置存储在注册表中,以便进行升级以找到正确的位置。例如用户设置自定义安装目录。<Property Id="INSTALLLOCATION"><RegistrySearch Id="RegistrySearch" Type="raw" Root="HKLM" Win64="$(var.Win64)" Key="Software\Company\Product" Name="InstallLocation" /></Property>

注意:更多关于从命令行中设置属性时的边界情况请见Rob Mensching博客

例1.2和1.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)">

  1. 升级是最简单的解决方法,因为它允许在单个MSI中同时安装和升级。UpgradeCode被固定在一个唯一的Guid上,并且不会改变。

:在Wix 3.5中有一个新的主要升级元素,使得这一切变得更容易

  1. 在添加/删除程序中创建图标<Icon Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" /><Property Id="ARPPRODUCTICON" Value="Company.ico" /><Property Id="ARPHELPLINK" Value="http://www.example.com/" />
  2. 在发布版本时,我们会对安装程序进行版本化,将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>

  1. 获取带有通配符(*)Guid的文件。如果你想在多个项目中重用WXS文件,这能起到一定作用。例如,这个批处理文件会自动收获RoboHelp的输出。 @echo off机器人拷贝..\WebHelp“%temp%\WebHelpTemp\WebHelp”/E/NP/Purge/XD。SVN%wix%bin\heatdir“%temp%\WebHelton-NoLogo-sfrag-suid-ag-SRD-dir WebHelp.wxs-CG WebHelpComponent-Dr INSTALLLOCATION-var.。WebDeploySourceDir

在获取之前,robocopy正在删除正在工作的复制元数据的Subversion;-dr根目录引用设置为我们的安装位置,而不是默认的TARGETDIR;-var用于创建一个变量来指定源目录(web部署输出)。

  1. 通过使用Strings.wxl进行本地化,可以轻松地将产品版本包含在欢迎对话框标题中。(Saschabeaumont)

<WixLocalization Culture="en-US" xmlns="http://schemas.microsoft.com/wix/2006/localization"><String Id="WelcomeDlgTitle">{\WixUI[医]字型[医]欢迎来到产品名称安装向导</String></WixLocalization>

  1. 遵循Win Coehen对每个文件一个组件的建议。这也允许你省略(或通配符*)组件的GUID
  2. RobMensching有一个干净利落通过搜索value 3来快速追踪MSI日志文件中的问题。
  3. 添加条件特性时,更直观的做法是将默认功能级别设置为0(禁用),然后将条件级别设置为所需的值。如果设置默认的特性级别>=1,则条件级别必须为0才能禁用它。

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

所属标签

可能回答问题的人

  • 西风

    renzha.net · 站长 (已认证)

    7 粉丝1 提问8 回答
  • 四无君

    0 粉丝0 提问3 回答
  • o o

    3 粉丝490 提问2 回答
  • Dingda

    Dingda · 站长 (已认证)

    4 粉丝0 提问2 回答

扫码关注云+社区

领取腾讯云代金券