WiX 技巧和技巧

发布于 2024-07-12 04:34:04 字数 198 浏览 4 评论 0 原文

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

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

We've been using WiX for a while now, and despite the usual gripes about ease of use, it's going reasonably well. What I'm looking for is useful advice regarding:

  • Setting up a WiX project (layout, references, file patterns)
  • Integrating WiX into solutions, and build/release processes
  • Configuring installers for new installations and upgrades
  • Any good WiX hacks you'd like to share

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

長街聽風 2024-07-19 04:34:05

使用 Javascript CustomActions 因为它们太简单了

人们说过Javascript 不适用于 MSI CustomActions。 给出的原因是:难以调试、难以使其可靠。 我不同意。 调试并不难,当然不比 C++ 难。 只是不同而已。 我发现用 Javascript 编写 CustomActions 非常简单,比使用 C++ 容易得多。 快多了。 并且同样可靠。

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

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


CustomAction 的 Javascript“样板”代码:

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


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

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

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

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


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

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


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


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

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

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

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

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

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

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

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

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

然后,使用如下内容注册自定义操作:

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

  <CustomAction Id="CA.MyCustomAction"
              BinaryKey="IisScript_CA"
              JScriptCall="MyCustomActionInJavascript_CA"
              Execute="immediate"
              Return="check" />
</Fragmemt>

当然,您可以为多个自定义操作插入任意数量的 Javascript 函数。 一个例子:我使用 Javascript 在 IIS 上执行 WMI 查询,以获取可以安装 ISAPI 过滤器的现有网站列表。 然后,该列表用于填充稍后在 UI 序列中显示的列表框。 一切都很容易。

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

相关问题:关于 Javascript CustomActions

Use Javascript CustomActions because they're soooo easy

People have said that Javascript is the wrong thing to use for MSI CustomActions. Reasons given: hard to debug, hard to make it reliable. I don't agree. It's not hard to debug, certainly no harder than C++. Its just different. I found writing CustomActions in Javascript to be super easy, much easier than using C++. Much faster. And just as reliable.

There's just one drawback: Javascript CustomActions can be extracted via Orca, whereas a C/C++ CA would require reverse-engineering. If you consider your installer magic to be protected intellectual property, you will want to avoid script.

If you use script,
you just need to start with some structure. Here's some to get you started.


Javascript "boilerplate" code for CustomAction:

//
// 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
    };
}

Then, register the custom action with something like this:

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

  <CustomAction Id="CA.MyCustomAction"
              BinaryKey="IisScript_CA"
              JScriptCall="MyCustomActionInJavascript_CA"
              Execute="immediate"
              Return="check" />
</Fragmemt>

You can, of course, insert as many Javascript functions as you like, for multiple custom actions. One example: I used Javascript to do a WMI query on IIS, to get a list of existing websites, to which an ISAPI filter could be installed. This list was then used to populate a listbox shown later in the UI sequence. All very easy.

On IIS7, there is no WMI provider for IIS, so I used the shell.Run() approach to invoke appcmd.exe to perform the work. Easy.

Related question: About Javascript CustomActions

妖妓 2024-07-19 04:34:05

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

目录别名

组件组片段不需要了解主产品 wxs 定义的目录。 在组件组片段中,您可以像这样讨论文件夹:

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

然后主产品可以为其目录之一(例如“productInstallFolder”)指定别名,如下所示:

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

依赖关系图

ComponentGroup 元素可以包含 ComponentGroupRef 子元素。 如果您有大量可重用组件,并且它们之间具有复杂的依赖关系图,那么这非常有用。 您只需在每个组件的自己的片段中设置一个 ComponentGroup 并声明依赖关系,如下所示:

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

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

可重用wixlib

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

Peter Tate has already shown how you can define reusable ComponentGroup definitions in separate wix fragments. Some additional tricks related to this:

Directory Aliasing

The component group fragments don't need to know about directories defined by the main product wxs. In your component group fragment you can talk about a folder like this:

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

Then the main product can alias one of its directories (e.g. "productInstallFolder") like this:

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

Dependency Graph

ComponentGroup elements can contain ComponentGroupRef child elements. This is great if you have a big pool of reusable components with a complex dependency graph between them. You just set up a ComponentGroup in its own fragment for each component and declare the dependencies like this:

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

If you now reference component group "B" in your setup because it is a direct dependency of your application, it will automatically pull in component group "A" even if the application author never realized that it was a dependency of "B". It "just works" as long as you don't have any circular dependencies.

Reusable wixlib

The above dependency graph idea works best if you compile the big-pool-o-reusable-components into a reusable wixlib with lit.exe. When creating an application setup, you can reference this wixlib much like a wixobj file. The candle.exe linker will automatically eliminate any fragments that are not "pulled in" by the main product wxs file(s).

迷荒 2024-07-19 04:34:05

我很惊讶没有人提到在构建过程中使用 T4 生成 WXS 文件。 我通过 Henry Lee @ 新时代解决方案

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

I'm surprised no one has mentioned using T4 to generate the WXS file during build. I learned about this by way of Henry Lee @ New Age Solutions.

Essentially, you create a custom MSBuild task to execute a T4 template, and that template outputs the WXS just before the Wix project is compiled. This allows you to (depending on how you implement it) automatically include all assemblies output from compiling another solution (meaning that you no longer have to edit the wxs ever time you add a new assembly).

无戏配角 2024-07-19 04:34:05

使用 Heat.exe 砸脸并在令人痛苦的大型安装中造成“Epic Pwnage”

扩展 Si
Robert-P 关于热量的回答。

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

WiX 2.0 Heat 语法详细信息

对于较新的版本(与旧版本并没有什么不同,但可能存在令人讨厌的语法更改...),请从 cmd.exe 转到 Heat 所在目录,然后输入 heat,但我这里有一个示例以寻求帮助如果需要,可以使用较新的版本。

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

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

-gg 

Heat 运行时生成指南(如执行上面的命令时)

-scom 

不要抓取“COM 文件”

-sreg 

不要抓取“注册表文件”

-sfrag 

不要抓取“片段”

-srd 

不要抓取“根目录”

dir

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

"$(EnviromentVariable)"

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

Example:
EnviromentVariable=C:\Project\bin\Debug;

No double quotes but end with a semicolon

-cg GroupVariable 

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

FragmentDir

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

FileName.wxs

的名称

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

第 1 部分
第 2 部分

Using Heat.exe to smash face and inflict "Epic Pwnage" on painfully large installs

Expanding on Si's and
Robert-P's answers about heat.

Translation:
(Using heat to avoid typing individual files into the project by hand and for automating builds for an overall easier process.)

WiX 2.0 Heat Syntax detailed

For newer versions (not all that different from older versions but there are potentially annoying syntax changes....) go to the directory Heat is in from the cmd.exe and just type in heat but I have a example one right here for help with newer versions if needed.

Adding the following to your Build Event in visual studio 2010.
(Right Click 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 

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

-scom 

Dont grab "COM files"

-sreg 

Dont grab "Registry Files"

-sfrag 

Dont grab "Fragments"

-srd 

Dont grab the "root Dir"

dir

dir indicates you want Heat to look in a folder

"$(EnviromentVariable)"

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

Example:
EnviromentVariable=C:\Project\bin\Debug;

No double quotes but end with a semicolon

-cg GroupVariable 

The ComponentGroup that will be referenced from the fragment created to the main wxs file

FragmentDir

The fragment directory where the output wxs fragment will be stored

FileName.wxs

The the name of the file

Full tutorial here, So freakin helpful

Part 1
Part 2

瑾兮 2024-07-19 04:34:05

包括 COM 对象:

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

包含托管 COM 对象(又名.NET 或 C# COM 对象)

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

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

但是,某些环境(尤其是任何具有托管内存的环境,例如脚本语言)也需要访问 Typelib。 确保在您的类型库上运行 heat 并包含它。 heat 将生成所有需要的注册表项。 多么酷啊?

Including COM Objects:

heat generates all most (if not all) the registry entries and other configuration needed for them. Rejoice!

Including Managed COM Objects (aka, .NET or C# COM objects)

Using heat on a managed COM object will give you an almost complete wix document.

If you don't need the library available in the GAC (ie, globally available: MOST of the time you do not need this with your .NET assemblies anyway - you've probably done something wrong at this point if it's not intended to be a shared library) you will want to make sure to update the CodeBase registry key to be set to [#ComponentName]. If you ARE planning on installing it to the GAC (eg, you've made some new awesome common library that everyone will want to use) you must remove this entry, and add two new attributes to the File element: Assembly and KeyPath. Assembly should be set to ".net" and KeyPath should be set to "yes".

However, some environments (especially anything with managed memory such as scripting languages) will need access to the Typelib as well. Make sure to run heat on your typelib and include it. heat will generate all the needed registry keys. How cool is that?

慕巷 2024-07-19 04:34:05

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

然后,在 标记下方的某个位置,您像往常一样需要以下目录标记:

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

Installing to C:\ProductName

Some applications need to be installed to C:\ProductName or something similar, but 99.9% (if not 100%) of the examples in the net install to C:\Program Files\CompanyName\ProductName.

The following code can be used to set the TARGETDIR property to the root of the C: drive (taken from the 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>

NOTE: By default, TARGETDIR does not point to C:\! It rather points to ROOTDRIVE which in turn points to the root of the drive with the most free space (see here) - and this is not necessarily the C: drive. There might be another hard drive, partition, or USB drive!

Then, somewhere below your <Product ...> tag, you need the following directory tags as usual:

<Directory Id="TARGETDIR" Name="SourceDir">
    <Directory Id="APPLICATIONFOLDER" Name="$(var.ProductName)">
        <!-- your content goes here... -->
    </Directory>
</Directory>
烧了回忆取暖 2024-07-19 04:34:05

环境变量

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

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

然后在代码中执行 以下操作,使用它来动态更改您的 wxs 文档,例如:

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

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)" />
绾颜 2024-07-19 04:34:05

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

http://www.codeproject.com/KB/安装/wixcustomaction.aspx

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

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

终止放荡 2024-07-19 04:34:05

编辑对话框

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

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 e.g. InstallDirDlg.wxs) can be opened, previewed and edited in Design view.

满意归宿 2024-07-19 04:34:05

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

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" />
指尖凝香 2024-07-19 04:34:05

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

它看起来像这样:
替代文本 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单击“下一步”时,将触发CA:

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

相关SO问题:如何在运行时将文本设置为在VerifyReadyDlg中显示?

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

It looks like this:
alt text http://i46.tinypic.com/s4th7t.jpg

Do this with a Javascript CustomAction:


Javascript code:

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

剪不断理还乱 2024-07-19 04:34:05

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

它适用于制作产品安装程序和补丁,如果您在片段中包含任何组件,则必须包含所有该片段中的组件。 在构建安装程序的情况下,如果您错过任何组件引用,您将收到来自 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 帮助文件中的“Using 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 是不够的在单独的片段中使用组件。 因此,请务必在发货前正确分割您的产品。

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.

like this:

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

instead of this:

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

战皆罪 2024-07-19 04:34:05

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

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

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

3) 您的许可证对话框必须具有 ID 为“LicenseText”的 ScrollableText 控件。 Wix 在打印时精确搜索该控件名称。

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

和一个引用自定义操作的 PushButton

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

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

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

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

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

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, i.e. 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.

我ぃ本無心為│何有愛 2024-07-19 04:34:05
  • 我们在 GUI 第一个屏幕的某个位置(很小)显示产品版本。 因为人们每次选择正确的版本时都容易犯错误。 (并且让我们开发人员搜索了很长时间......)

  • 我们已经设置了 TFSBuild,还可以使用不同环境的配置生成转换(.mst 文件)。 (我们了解需要部署到的所有环境)。

由于 Grant Holliday 的原始博客文章已关闭,我将其内容复制粘贴到此处:


MSBuild 任务从 XML 生成 MSI 转换文件 2008 年 3 月 11 日

在我的上一篇文章中,我描述了如何使用 MSI 转换 (*.mst) 文件来分离环境- 来自通用 MSI 包的特定配置设置。

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

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

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

积分再次转到Alex Shevchuk – 从 MSI 到 WiX – 第 7 部分 – 使用 Transforms 自定义安装,向我们展示如何使用 VbScript 实现这一目标。 本质上,我所做的只是采用 Alex 的示例,并使用 Interop.WindowsInstaller.dll 实现了 MSBuild 任务。
MSBuild 任务

下载< /a> 源代码 & 这里有transforms.xml 示例(~7Kb 压缩的 VS2008 解决方案)


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


旧人哭 2024-07-19 04:34:05

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

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

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

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

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.

陌伤浅笑 2024-07-19 04:34:05

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

此外,还提供具有附加功能的 Plus 版本。 而且免费版本也为 Orca 提供了一个很好的替代方案。

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.

时光匆匆的小流年 2024-07-19 04:34:05

为具有 x86/x64 兼容性的 COM Interop 注册 .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 望远镜驱动程序

首先,我采纳了上面的建议,并在一个单独的文件中创建了一些 platforma 变量,您可以看到这些变量分散在 XML 中。

顶部附近的 if-then-else 部分处理 x86 与 x64 兼容性。 我的程序集面向“任何 CPU”,因此在 x64 系统上,我需要将其注册两次,一次在 64 位注册表中,一次在 32 位 Wow6432Node 区域中。 if-then-else 对此进行了设置,这些值稍后将在 foreach 循环中使用。 这样,我只需编写一次注册表项(DRY 原则)。

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

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

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

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.

谜泪 2024-07-19 04:34:05

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

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

我认为 Windows Installer 4.0 或更高版本是要求< /a>.

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.

一袭白衣梦中忆 2024-07-19 04:34:05

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

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

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

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?

歌入人心 2024-07-19 04:34:05

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

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

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.

俯瞰星空 2024-07-19 04:34:05

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

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)

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)
终遇你 2024-07-19 04:34:05

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

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

Wix 与条件、属性和属性的交互 自定义操作

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

沙与沫 2024-07-19 04:34:04
  1. 将变量保存在单独的 wxi 包含文件中。 允许重用,可以更快地查找变量,并且(如果需要)可以更轻松地通过外部工具进行操作。

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

     
       
         
         
         
      <?其他?> 
         
        <?定义 Win64 =“否”?> 
         
       
      
  3. 将安装位置存储在注册表中,以便升级能够找到正确的位置。 例如,如果用户设置自定义安装目录。

     <属性 ID="INSTALLLOCATION"> 
           
        
      

    注意:WiX 大师 Rob Mensching 发布了 优秀的博客条目,其中详细介绍了修复了从命令行设置属性时的边缘情况。

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

     
      <产品...> 
         
      

    <目录 Id="TARGETDIR" Name="SourceDir"> 
        <目录 Id="$(var.PlatformProgramFilesFolder)"> 
          <目录 ID="INSTALLLOCATION" 名称="$(var.InstallName)"> 
      
  4. 最简单的方法始终是 主要升级,因为它允许在单个 MSI 中进行新安装和升级。 UpgradeCode 固定为唯一的 Guid 并且永远不会改变,除非我们不想升级现有产品。

    注意:在 WiX 3.5 中,有一个新的 MajorUpgrade 元素使生活均匀更容易

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

    <图标 Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" /> 
      <属性 Id="ARPPRODUCTICON" Value="Company.ico" /> 
      <属性 Id="ARPHELPLINK" Value="http://www.example.com/" /> 
      
  6. 在发布版本中,我们对安装程序进行版本控制,将 msi 文件复制到部署目录。 使用从 AfterBuild 目标调用的 wixproj 目标的示例:

     
         
        <复制 SourceFiles="$(OutputPath)$(OutputName).msi"  
              DestinationFiles="..\Deploy\Setup\$(OutputName) $(AssemblyFileVersion)_$(Platform).msi" /> 
       
      
  7. 使用 heat 获取带有通配符 (*) Guid 的文件。 如果您想在多个项目中重用 WXS 文件,这很有用(请参阅我对同一产品的多个版本的回答)。 例如,此批处理文件自动收集 RoboHelp 输出。

    @echo 关闭   
      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 安装 -var var.WebDeploySourceDir  
      

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

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

     
          {\WixUI_Font_Bigger}欢迎使用 [ProductName] [ProductVersion] 安装向导 
       
      
  9. 为自己省去一些麻烦,然后按照< a href="https://stackoverflow.com/questions/1602831/wix-one-file-per-component-or-several-files-per-component/1604348#1604348">Wim Coehen 的建议之一每个文件的组件。 这还允许您省略(或通配符 *组件 GUID

  10. Rob Mensching 有一个 通过搜索值 3 快速追踪 MSI 日志文件中问题的巧妙方法。 请注意有关国际化的评论。

  11. 添加条件功能时,更直观的做法是将默认功能级别设置为 0(禁用),然后将条件级别设置为所需的值。 如果您设置默认功能级别 >= 1,则条件级别必须为 0 才能禁用它,这意味着条件逻辑必须与您期望的相反,这可能会令人困惑:)

    <功能 Id="NewInstallFeature" Level="0" 描述="新安装功能" Absent="allow"> 
        <条件级别=“1”>未升级 
       
      <功能 Id="UpgradeFeature" Level="0" 描述="升级功能" Absent="允许"> 
        <条件级别=“1”>UPGRADEFOUND 
       
      
  1. Keep variables in a separate wxi include file. Enables re-use, variables are faster to find and (if needed) allows for easier manipulation by an external tool.

  2. Define Platform variables for x86 and x64 builds

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

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

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

    Examples using 1. 2. and 3.

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

    and

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

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

  5. Creating an icon in Add/Remove Programs

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

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

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

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

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

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

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

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

    <Feature Id="NewInstallFeature" Level="0" Description="New installation feature" Absent="allow">
      <Condition Level="1">NOT UPGRADEFOUND</Condition>
    </Feature>
    <Feature Id="UpgradeFeature" Level="0" Description="Upgrade feature" Absent="allow">
      <Condition Level="1">UPGRADEFOUND</Condition>
    </Feature>
    
坦然微笑 2024-07-19 04:34:04

检查是否安装了 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 < 600) OR IIS_METABASE_COMPAT)
</Condition>

Checking if IIS is installed:

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

Checking if IIS 6 Metabase Compatibility is installed on Vista+:

<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 < 600) OR IIS_METABASE_COMPAT)
</Condition>
演多会厌 2024-07-19 04:34:04

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

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

我发现这对于跟踪所有不同类别中的所有不同 ID 有很大帮助。

Keep all IDs in separate namespaces

  • Features begin with F. Examples: F.Documentation, F.Binaries, F.SampleCode.
  • Components begin with C. Ex: C.ChmFile, C.ReleaseNotes, C.LicenseFile, C.IniFile, C.Registry
  • CustomActions are CA. Ex: CA.LaunchHelp, CA.UpdateReadyDlg, CA.SetPropertyX
  • Files are Fi.
  • Directories are Di.
  • and so on.

I find this helps immensely in keeping track of all the various id's in all the various categories.

合久必婚 2024-07-19 04:34:04

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

我有很多要分发的文件,因此我将项目设置为几个 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 的功能标记中引用。 我的项目包含我分发的文件的良好逻辑分组

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

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

我很想听到对此的评论,或者是否有人也有任何好的建议!

Fantastic question. I'd love to see some best practices shown.

I've got a lot of files that I distribute, so I've set up my project into several wxs source files.

I have a top level source file which I call Product.wxs which basically contains the structure for the installation, but not the actual components. This file has several sections:

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

The rest of the .wix files are composed of Fragments that contain ComponentGroups which are referenced in the Feature tag in the Product.wxs. My project contains a nice logical grouping of the files that I distribute

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

This isn't perfect, my OO spider sense tingles a bit because the fragments have to reference names in the Product.wxs file (e.g. the DirectoryRef) but I find it easier to maintain that a single large source file.

I'd love to hear comments on this, or if anyone has any good tips too!

梦一生花开无言 2024-07-19 04:34:04

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

...

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

如果这样做,“标准”外观就不太正确。 复选框始终为灰色背景,而对话框为白色:

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

一种方法围绕这个问题是指定您自己的自定义ExitDialog,具有不同位置的复选框。 这可行,但似乎只是为了改变一个控件的颜色就需要做很多工作。 解决同一问题的另一种方法是对生成的 MSI 进行后处理,以更改该特定 CheckBox 控件的控制表中的 X、Y 字段。 javascript 代码如下所示:

var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql = "UPDATE `Control` SET `Control`.`Height` = '18', `Control`.`Width` = '170'," +
          " `Control`.`Y`='243', `Control`.`X`='10' " +
          "WHERE `Control`.`Dialog_`='ExitDialog' AND " + 
          "  `Control`.`Control`='OptionalCheckBox'";
var view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();

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

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

Add a checkbox to the exit dialog to launch the app, or the helpfile.

...

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

If you do it this way, the "standard" appearance isn't quite right. The checkbox is always a gray background, while the dialog is white:

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

One way around this is to specify your own custom ExitDialog, with a differently-located checkbox. This works, but seems like a lot of work just to change the color of one control. Another way to solve the same thing is to post-process the generated MSI to change the X,Y fields in the Control table for that particular CheckBox control. The javascript code looks like this:

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();

Running this code as a command-line script (using cscript.exe) after the MSI is generated (from light.exe) will produce an ExitDialog that looks more professional:

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

追我者格杀勿论 2024-07-19 04:34:04

使用相同的源文件创建实时、测试、培训等版本。

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

先决条件

假设

  • WiX 变量用于定义 UpgradeCode、ProductName、InstallName。
  • 您已经有一个可用的安装程序。 在你这样做之前我不会尝试这个。
  • 您的所有组件都保存在一个文件 (Components.wxs) 中。 如果您有多个文件,此过程将起作用,只是需要做更多工作。

目录结构

  • Setup.Library
    • 所有 wxs 文件(组件、功能、UI 对话框...)
    • Common.Config.wxi(ProductCode="*"、ProductVersion、PlatformProgramFilesFolder、...)
  • Setup.Live (维克斯项目)
    • 使用“添加现有文件”链接所有 Setup.Library 文件 -> “添加为链接”(Visual Studio 中“添加”按钮旁边的小向下箭头按钮)
    • 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来修改Guids(我使用A用于Live,B用于测试,C用于训练)
  • 添加AfterBuild目标以恢复Components.wxs Guid 返回到 0。
  • 使用 Orca 验证每个 MSI 中的每个组件是否具有修改后的 GUID。
  • 验证原始指南是否已恢复。
  • 验证每个 MSI 是否正在安装(和升级)正确的产品和位置。

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

Example 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 之外(相当于*),每个组件使用一个文件并将该文件设置为密钥路径。 这样就无需调用如下所示的 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="([a-f]|[A-F]|\d)" ReplacementText="Guid="A" />
</Target>
<!-- Revert the first character of every Guid back to initial value -->
<Target Name="RevertComponentsGuids">
  <FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid="([a-f]|[A-F]|\d)" ReplacementText="Guid="0" />
</Target>

最终想法

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

更新1: 自动生成组件指南 如果您为每个文件创建带有 Guid="*" 的组件,并将文件设置为键路径,则无需调用 FileUpdate 任务。

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

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

更新4:支持

Creating Live, Test, Training, ... versions using the same source files.

In a nutshell: Create unique UpgradeCode for each installer and automagically define the first character of each Guid for each installer, leaving the remaining 31 unique.

Prerequisites

Assumptions

  • WiX variables are used to define UpgradeCode, ProductName, InstallName.
  • You already have a working installer. I wouldn't attempt this until you do.
  • All your Components are kept in one file (Components.wxs). This process will work if you have multiple files, there will just be more work to do.

Directory Structure

  • Setup.Library
    • All wxs files (Components, Features, UI Dialogs, ...)
    • Common.Config.wxi (ProductCode="*", ProductVersion, PlatformProgramFilesFolder, ...)
  • Setup.Live (wixproj)
    • Link all Setup.Library files using "Add Existing File" -> "Add As Link" (the little down arrow button right next to the Add button in Visual Studio)
    • Config.wxi (has unique UpgradeCode, ProductName, InstallName, ...)
  • Setup.Test, ...
    • as per live but Config.wxi is configured for Test environment.

Process

  • Create Setup.Library directory and move all your wxs and wxi files (except Config.wxi) from existing project.
  • Create Setup.Live, Setup.Test, etc as per normal wixproj.
  • Add BeforeBuild target in wixproj in Setup.Live, etc to perform MSBuild Community Task FileUpdate to modify Guids (I used A for Live, B for Test and C for training)
  • Add AfterBuild target to revert Components.wxs Guids back to 0.
  • Verify with Orca that each component in each MSI has the modified guid.
  • Verify that original guids are restored.
  • Verify that each MSI is installing (and upgrading) correct product and location.

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

Example 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">
          ...

Note: I would now suggest leaving the Guid attribute out of Component (equivalent of *), using one file per component and setting the file as the keypath. This removes the need for calling ModifyComponentsGuids and RevertComponentsGuids targets shown below. This might not be possible for all your components though.

Example 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="([a-f]|[A-F]|\d)" ReplacementText="Guid="A" />
</Target>
<!-- Revert the first character of every Guid back to initial value -->
<Target Name="RevertComponentsGuids">
  <FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid="([a-f]|[A-F]|\d)" ReplacementText="Guid="0" />
</Target>

Final thoughts

  • This process should also work for creating different installers for different merge modules (Live, Test, ... as features) for the same installer. I went with different installers as it seemed a safer option, there is more risk that someone might upgrade Live instead of Training if they're on the same box and you just use features for the different merge modules.
  • If you use your MSI to perform upgrades as well as new installs i.e. the major upgrade only approach, and you save your installation location in the registry, remember to create a variable for the key name for each install.
  • We also create variables in each Config.wxi to enable unique virtual directory names, application pools, database names, et cetera for each installer.

UPDATE 1: Auto-generating component Guids removes the need for calling FileUpdate task if you create component with Guid="*" for each file, setting the file as the keypath.

UPDATE 2: One of the issues we've come up against is if you don't auto-generate your component Guid's and the build fails, then the temp files need to be manually deleted.

UPDATE 3: Found a way to remove reliance on svn:externals and temporary file creation. This makes the build process more resilient (and is best option if you can't wildcard your Guids) and less brittle if there is a build failure in light or candle.

UPDATE 4: Support for Multiple Instances using instance transforms is in WiX 3.0+, definitely also worth a look.

可是我不能没有你 2024-07-19 04:34:04

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

<代码>
msiexec /i Package.msi /l*vc:\Package.log

在哪里

Package.msi

is the name of your package
and

c:\Package.log

is where you want the output of the log

Msi 错误代码

Wix 介绍视频
哦,Random Wix 的介绍视频“Mr. WiX”Rob Mensching 很有帮助。

Using the Msi Diagnostic logging to get detailed failure Information


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

Where

Package.msi

is the name of your package
and

c:\Package.log

is where you want the output of the log

Msi Error Codes

Wix Intro Video
Oh and Random Wix intro video featuring "Mr. WiX" Rob Mensching is "conceptual big picture" helpful.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文