RT
事实上,你可以创建一个自定义MSBuild任务来执行T4模板,并且该模板需要在编译Wix项目之前输出WXS。这允许你自动包含编译另一个解决方案的所有程序集输出(这意味着你在添加新程序集时不再需要编辑WXS)。
以下技巧是关于在单独的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文件“拉入”的片段。
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来执行工作。