我们现在已经使用WiX一段时间了,尽管人们普遍抱怨易用性,但它还是相当不错。我正在寻找的是有用的建议:
答案 0 :(得分:157)
将变量保存在单独的wxi
包含文件中。允许重复使用,变量更快找到,并且(如果需要)允许外部工具更容易操作。
为x86和x64版本定义平台变量
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?if $(var.Platform) = x64 ?>
<?define ProductName = "Product Name (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define ProductName = "Product Name" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
将安装位置存储在注册表中,启用升级以查找正确的位置。例如,如果用户设置自定义安装目录。
<Property Id="INSTALLLOCATION">
<RegistrySearch Id="RegistrySearch" Type="raw" Root="HKLM" Win64="$(var.Win64)"
Key="Software\Company\Product" Name="InstallLocation" />
</Property>
注意:WiX guru Rob Mensching发布了一个excellent blog entry,其中详细介绍了从命令行设置属性时的边缘情况。
使用1. 2.和3的例子。
<?include $(sys.CURRENTDIR)\Config.wxi?>
<Product ... >
<Package InstallerVersion="200" InstallPrivileges="elevated"
InstallScope="perMachine" Platform="$(var.Platform)"
Compressed="yes" Description="$(var.ProductName)" />
和
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
最简单的方法始终是major upgrades,因为它允许单个MSI中的新安装和升级。 UpgradeCode已修复为唯一的Guid,除非我们不想升级现有产品,否则永远不会更改。
注意:在WiX 3.5中,有一个新的MajorUpgrade元素可以生命even easier!
在“添加/删除程序”中创建图标
<Icon Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" />
<Property Id="ARPPRODUCTICON" Value="Company.ico" />
<Property Id="ARPHELPLINK" Value="http://www.example.com/" />
在发布版本中,我们对安装程序进行了版本控制,将msi文件复制到部署目录。使用从AfterBuild目标调用的wixproj目标的示例:
<Target Name="CopyToDeploy" Condition="'$(Configuration)' == 'Release'">
<!-- Note we append AssemblyFileVersion, changing MSI file name only works with Major Upgrades -->
<Copy SourceFiles="$(OutputPath)$(OutputName).msi"
DestinationFiles="..\Deploy\Setup\$(OutputName) $(AssemblyFileVersion)_$(Platform).msi" />
</Target>
使用heat来收集带有通配符(*)Guid的文件。如果您想在多个项目中重用WXS文件,则非常有用(请参阅我对同一产品的多个版本的回答)。例如,此批处理文件会自动收集RoboHelp输出。
@echo off
robocopy ..\WebHelp "%TEMP%\WebHelpTemp\WebHelp" /E /NP /PURGE /XD .svn
"%WIX%bin\heat" dir "%TEMP%\WebHelp" -nologo -sfrag -suid -ag -srd -dir WebHelp -out WebHelp.wxs -cg WebHelpComponent -dr INSTALLLOCATION -var var.WebDeploySourceDir
还有一点,robocopy
在收获之前剥离Subversion工作副本元数据; -dr
根目录引用设置为我们的安装位置,而不是默认的TARGETDIR; -var
用于创建变量以指定源目录(Web部署输出)。
通过使用Strings.wxl进行本地化,可以轻松地将产品版本包含在欢迎对话框标题中。 (信用:saschabeaumont。添加,因为这个伟大的提示隐藏在评论中)
<WixLocalization Culture="en-US" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="WelcomeDlgTitle">{\WixUI_Font_Bigger}Welcome to the [ProductName] [ProductVersion] Setup Wizard</String>
</WixLocalization>
为自己省去一些痛苦,并按照每个文件的一个组件Wim Coehen's advice进行操作。这也允许您遗漏(或通配符*
)component GUID。
Rob Mensching有一个neat way,可以通过搜索value 3
来快速跟踪MSI日志文件中的问题。请注意有关国际化的评论。
添加条件功能时,将默认功能级别设置为0(禁用)更直观,然后将条件级别设置为所需的值。如果您设置默认要素级别&gt; = 1,则条件级别必须为0才能将其禁用,这意味着条件逻辑必须与您期望的相反,这可能会令人困惑:)
<Feature Id="NewInstallFeature" Level="0" Description="New installation feature" Absent="allow">
<Condition Level="1">NOT UPGRADEFOUND</Condition>
</Feature>
<Feature Id="UpgradeFeature" Level="0" Description="Upgrade feature" Absent="allow">
<Condition Level="1">UPGRADEFOUND</Condition>
</Feature>
答案 1 :(得分:38)
检查是否安装了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>
答案 2 :(得分:34)
将所有ID保存在不同的名称空间中
F.
开头示例:F.Documentation,F.Binaries,F.SampleCode。 C.
开头Ex:C.ChmFile,C.ReleaseNotes,C.LicenseFile,C.IniFile,C.Registry CA.
例如:CA.LaunchHelp,CA.UpdateReadyDlg,CA.SetPropertyX Fi.
Di.
我发现这有助于跟踪所有各种类别中的各种ID。
答案 3 :(得分:25)
很棒的问题。我很想看到一些最佳实践。
我分发了很多文件,因此我将项目设置为几个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的Fragments组成,这些ComponentGroups在Product.wxs的Feature标记中引用。我的项目包含一个很好的逻辑分组,我分发的文件
<Fragment>
<ComponentGroup>
<ComponentRef>
....
</ComponentGroup>
<DirectoryRef>
<Component... for each file
....
</DirectoryRef>
</Fragment>
这并不完美,我的OO蜘蛛感觉有点刺痛,因为片段必须引用Product.wxs文件中的名称(例如DirectoryRef),但我发现维护单个大型源文件更容易。
我很想听到有关此的评论,或者是否有人也有任何好的提示!
答案 4 :(得分:20)
在退出对话框中添加一个复选框以启动应用或帮助文件。
...
<!-- 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>
如果你这样做,“标准”外观不太对劲。复选框始终为灰色背景,而对话框为白色:
解决这个问题的方法之一是specify your own custom ExitDialog, with a differently-located checkbox。这可行,但似乎只是为了改变一个控件的颜色很多工作。解决同一问题的另一种方法是对生成的MSI进行后处理,以更改特定CheckBox控件的Control表中的X,Y字段。 javascript代码如下所示:
var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql = "UPDATE `Control` SET `Control`.`Height` = '18', `Control`.`Width` = '170'," +
" `Control`.`Y`='243', `Control`.`X`='10' " +
"WHERE `Control`.`Dialog_`='ExitDialog' AND " +
" `Control`.`Control`='OptionalCheckBox'";
var view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();
在生成MSI之后(从light.exe)将此代码作为命令行脚本(使用cscript.exe)运行,将生成看起来更专业的ExitDialog:
答案 5 :(得分:18)
简而言之:为每个安装程序创建唯一的UpgradeCode,并为每个安装程序自动定义每个Guid的第一个字符,剩下的31个唯一。
示例Config.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Upgrade code should not change unless you want to install
a new product and have the old product remain installed,
that is, both products existing as separate instances. -->
<?define UpgradeCode = "YOUR-GUID-HERE" ?>
<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?define ProductName = "Foo 64 Bit [Live]" ?>
<?else ?>
<?define ProductName = "Foo [Live]" ?>
<?endif ?>
<!-- Directory name used as default installation location -->
<?define InstallName = "Foo [Live]" ?>
<!-- Registry key name used to store installation location -->
<?define InstallNameKey = "FooLive" ?>
<?define VDirName = "FooLive" ?>
<?define AppPoolName = "FooLiveAppPool" ?>
<?define DbName = "BlahBlahLive" ?>
</Include>
示例Config.Common.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Auto-generate ProductCode for each build, release and upgrade -->
<?define ProductCode = "*" ?>
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define ProductVersion = "1.0.0.0" ?>
<!-- Minimum version supported if product already installed and this is an upgrade -->
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define MinimumUpgradeVersion = "0.0.0.0" ?>
<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<?define ProductManufacturer = "Foo Technologies"?>
<!-- Decimal Language ID (LCID) for the Product. Used for localization. -->
<?define ProductLanguage = "1033" ?>
<?define WebSiteName = "DefaultWebSite" ?>
<?define WebSitePort = "80" ?>
<?define DbServer = "(local)" ?>
</Include>
示例Components.wxs
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- The pre-processor variable which allows the magic to happen :) -->
<?include $(sys.CURRENTDIR)\Config.wxi?>
<?include ..\Setup.Library\Config.Common.wxi?>
<Fragment Id="ComponentsFragment">
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
<Component Id="ProductComponent" Guid="0XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" KeyPath="yes">
...
注意:我现在建议将Guid属性从Component(相当于*
)中删除,每个组件使用一个文件并将文件设置为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="([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>
最后的想法
更新1: Auto-generating component Guids如果为每个文件创建Guid =“*”的组件,将文件设置为键路径,则无需调用FileUpdate任务。
更新2:我们遇到的一个问题是,如果您不自动生成组件Guid并且构建失败,那么临时文件需要是手动删除。
更新3:找到一种方法来消除对svn:externals和临时文件创建的依赖。这使得构建过程更具弹性(如果您不能使用Guids通配符,则是最佳选择),如果在光线或蜡烛中存在构建失败,则不会那么脆弱。
更新4 :使用实例转换对Multiple Instances的支持在WiX 3.0+中,绝对值得一看。
答案 6 :(得分:17)
使用Msi诊断日志记录获取详细的故障信息
msiexec /i Package.msi /l*v c:\Package.log
其中
Package.msi是您的包裹的名称 并且
c:\Package.log是您希望输出日志的位置
Wix介绍视频
Oh and Random Wix intro video featuring "Mr. WiX" Rob Mensching is "conceptual big picture" helpful.
答案 7 :(得分:17)
使用Javascript CustomActions,因为它们非常容易
人们已经说Javascript is the wrong thing to use for 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来执行工作。容易。
答案 8 :(得分:12)
我很惊讶没有人提到使用T4在构建期间生成WXS文件。我通过Henry Lee @ New Age Solutions了解到这一点。
基本上,您创建一个自定义MSBuild任务来执行T4模板,该模板在Wix项目编译之前输出WXS。这允许您(取决于您如何实现它)自动包含编译另一个解决方案的所有程序集输出(这意味着您不再需要在添加新程序集时编辑wx)。
答案 9 :(得分:12)
使用Heat.exe粉碎脸部并在痛苦的大型安装上造成“Epic Pwnage”
扩展Si's和
Robert-P's关于热量的答案。
翻译:
(使用热量来避免手动将单个文件键入项目,并自动化构建以实现整体更简单的过程。)
WiX 2.0 Heat Syntax detailed
对于较新的版本(并非与旧版本有所不同,但可能会有令人烦恼的语法更改....)从cmd.exe进入目录Heat并输入加热,但我有一个例子如果需要,可以在这里获得更新版本的帮助。
将以下内容添加到visual studio 2010中的构建事件中
(右键单击项目 - &gt;属性 - &gt;构建事件 - &gt;预构建事件)
$(WIX)bin\heat.exe" dir "$(EnviromentVariable)" -cg GroupVariable -gg -scom -sreg -sfrag -
srd -dr INSTALLLOCATION -var env.LogicPath -out "$(FragmentDir)\FileName.wxs
-gg
在加热时生成Guids(如执行上述命令时)
-scom
不要抓取“COM文件”
-sreg
不要抓取“注册表文件”
-sfrag
不要抓住“碎片”
-srd
不要抓住“根目录”
dir
dir表示您希望Heat查看文件夹
"$(EnviromentVariable)"
您将添加到(右键单击项目,转到属性)项目属性中的预处理器变量的名称 - &gt;构建部分,其中显示定义预处理器变量(假定Visual Studio 2010)
Example: EnviromentVariable=C:\Project\bin\Debug;没有双引号但以分号结尾
-cg GroupVariable
将从创建到主wxs文件的片段引用的ComponentGroup
FragmentDir
将存储输出wxs片段的片段目录
FileName.wxs
文件名
这里有完整的教程,所以非常有用
答案 10 :(得分:12)
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文件未“拉入”的任何片段。
答案 11 :(得分:9)
包括COM对象:
heat
生成所有大多数(如果不是全部)注册表项以及它们所需的其他配置。飘柔!
包括托管COM对象(又名.NET或C#COM对象)
在托管COM对象上使用heat
将为您提供几乎完整的wix文档。
如果您不需要GAC中提供的库(即,全局可用:大多数时候您还不需要使用.NET程序集 - 如果不是这样的话,您可能在这一点上做错了打算成为共享库)您需要确保更新CodeBase
注册表项以设置为[#ComponentName]
。如果您计划将其安装到GAC(例如,您已经创建了一些每个人都想要使用的新的公共库),则必须删除此条目,并向File
元素添加两个新属性:{ {1}}和Assembly
。程序集应设置为“.net”,KeyPath
应设置为“是”。
但是,某些环境(尤其是具有托管内存的任何环境,如脚本语言)也需要访问Typelib。 Make sure to run heat
on your typelib并包含它。 KeyPath
将生成所有必需的注册表项。这有多酷?
答案 12 :(得分:8)
安装到C:\ProductName
某些应用程序需要安装到C:\ProductName
或类似的东西,但99.9%(如果不是100%)的网络安装示例C:\Program Files\CompanyName\ProductName
。
以下代码可用于将TARGETDIR
属性设置为C:
驱动器的根目录(取自WiX-users list):
<CustomAction Id="AssignTargetDir" Property="TARGETDIR" Value="C:\" Execute="firstSequence" />
<InstallUISequence>
<Custom Action="AssignTargetDir" Before="CostInitialize">TARGETDIR=""</Custom>
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="AssignTargetDir" Before="CostInitialize">TARGETDIR=""</Custom>
</InstallExecuteSequence>
注意:默认情况下,TARGETDIR
不指向C:\
!它指向ROOTDRIVE
,而C:
又指向具有最多可用空间的驱动器的根(see here) - 这不一定是<Product ...>
驾驶。可能有另一个硬盘驱动器,分区或USB驱动器!
然后,在<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="APPLICATIONFOLDER" Name="$(var.ProductName)">
<!-- your content goes here... -->
</Directory>
</Directory>
标记下方的某处,您需要照常使用以下目录标记:
{{1}}
答案 13 :(得分:7)
环境变量
将Wxs文档编译为wixobj代码时,可以使用环境变量来确定各种信息。例如,假设您要更改项目中包含的文件。假设您有一个名为RELEASE_MODE的环境变量,您可以在构建MSI之前进行设置(使用脚本或手动,无关紧要)在wix源代码中,您可以执行以下操作:
<define FILESOURCE = c:\source\output\bin\$(env.RELEASE_MODE) >
然后在您的代码中,使用它来动态更改您的wxs文档,例如:
<Icon Id="myicon.ico" SourceFile="$(var.FILESOURCE)" />
答案 14 :(得分:7)
编辑对话框
编辑对话框的一个很好的功能是在版本4.0.1.7090(或更高版本)中使用SharpDevelop。借助此工具,可以在“设计”视图中打开,预览和编辑独立对话框(来自WiX源的wxs文件,例如InstallDirDlg.wxs)。
答案 15 :(得分:7)
为没有Votive的托管代码(C#)编写的WIX创建自定义操作
答案 16 :(得分:7)
使用RobM特殊“记住属性”模式
http://robmensching.com/blog/posts/2010/5/2/The-WiX-toolsets-Remember-Property-pattern
答案 17 :(得分:6)
设置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" />
答案 18 :(得分:5)
修改“准备安装?”对话框(又名VerifyReadyDlg)提供所做选择的摘要。
看起来像这样:
alt text http://i46.tinypic.com/s4th7t.jpg
使用Javascript CustomAction执行此操作:
Javascript代码:
// http://msdn.microsoft.com/en-us/library/aa372516(VS.85).aspx
var MsiViewModify =
{
Refresh : 0,
Insert : 1,
Update : 2,
Assign : 3,
Replace : 4,
Merge : 5,
Delete : 6,
InsertTemporary : 7, // cannot permanently modify the MSI during install
Validate : 8,
ValidateNew : 9,
ValidateField : 10,
ValidateDelete : 11
};
// http://msdn.microsoft.com/en-us/library/sfw6660x(VS.85).aspx
var Buttons =
{
OkOnly : 0,
OkCancel : 1,
AbortRetryIgnore : 2,
YesNoCancel : 3
};
var Icons=
{
Critical : 16,
Question : 32,
Exclamation : 48,
Information : 64
}
var MsgKind =
{
Error : 0x01000000,
Warning : 0x02000000,
User : 0x03000000,
Log : 0x04000000
};
// http://msdn.microsoft.com/en-us/library/aa371254(VS.85).aspx
var MsiActionStatus =
{
None : 0,
Ok : 1, // success
Cancel : 2,
Abort : 3,
Retry : 4, // aka suspend?
Ignore : 5 // skip remaining actions; this is not an error.
};
function UpdateReadyDialog_CA(sitename)
{
try
{
// can retrieve properties from the install session like this:
var selectedWebSiteId = Session.Property("MSI_PROPERTY_HERE");
// can retrieve requested feature install state like this:
var fInstallRequested = Session.FeatureRequestState("F.FeatureName");
var text1 = "This is line 1 of text in the VerifyReadyDlg";
var text2 = "This is the second line of custom text";
var controlView = Session.Database.OpenView("SELECT * FROM Control");
controlView.Execute();
var rec = Session.Installer.CreateRecord(12);
rec.StringData(1) = "VerifyReadyDlg"; // Dialog_
rec.StringData(2) = "CustomVerifyText1"; // Control - can be any name
rec.StringData(3) = "Text"; // Type
rec.IntegerData(4) = 25; // X
rec.IntegerData(5) = 60; // Y
rec.IntegerData(6) = 320; // Width
rec.IntegerData(7) = 85; // Height
rec.IntegerData(8) = 2; // Attributes
rec.StringData(9) = ""; // Property
rec.StringData(10) = vText1; // Text
rec.StringData(11) = ""; // Control_Next
rec.StringData(12) = ""; // Help
controlView.Modify(MsiViewModify.InsertTemporary, rec);
rec = Session.Installer.CreateRecord(12);
rec.StringData(1) = "VerifyReadyDlg"; // Dialog_
rec.StringData(2) = "CustomVerifyText2"; // Control - any unique name
rec.StringData(3) = "Text"; // Type
rec.IntegerData(4) = 25; // X
rec.IntegerData(5) = 160; // Y
rec.IntegerData(6) = 320; // Width
rec.IntegerData(7) = 65; // Height
rec.IntegerData(8) = 2; // Attributes
rec.StringData(9) = ""; // Property
rec.StringData(10) = text2; // Text
rec.StringData(11) = ""; // Control_Next
rec.StringData(12) = ""; // Help
controlView.Modify(MsiViewModify.InsertTemporary, rec);
controlView.Close();
}
catch (exc1)
{
Session.Property("CA_EXCEPTION") = exc1.message ;
LogException("UpdatePropsWithSelectedWebSite", exc1);
return MsiActionStatus.Abort;
}
return MsiActionStatus.Ok;
}
function LogException(loc, exc)
{
var record = Session.Installer.CreateRecord(0);
record.StringData(0) = "Exception {" + loc + "}: " + exc.number + " : " + exc.message;
Session.Message(MsgKind.Error + Icons.Critical + Buttons.btnOkOnly, record);
}
声明Javascript CA:
<Fragment>
<Binary Id="IisScript_CA" SourceFile="CustomActions.js" />
<CustomAction Id="CA.UpdateReadyDialog"
BinaryKey="IisScript_CA"
JScriptCall="UpdateReadyDialog_CA"
Execute="immediate"
Return="check" />
</Fragment>
将CA附加到按钮。在此示例中,从CustomizeDlg:
单击Next时将触发CA.<UI ...>
<Publish Dialog="CustomizeDlg" Control="Next" Event="DoAction"
Value="CA.UpdateReadyDialog" Order="1"/>
</UI>
相关SO问题:How can I set, at runtime, the text to be displayed in VerifyReadyDlg?
答案 19 :(得分:5)
在部署安装包之前,我总是控制它的内容。
这只是在命令行上的一个简单调用(根据Terrences帖子)打开命令行并输入
msiexec /a Package.msi /qb TARGETDIR="%CD%\Extract" /l*vx "%CD\install.log%"
这将使用当前路径将包内容提取到子目录'Extract'。
答案 20 :(得分:5)
1)编译wix源代码时,light.exe必须在命令行中引用WixUIExtension.dll。使用命令行开关-ext。
2)如果添加对WixUIExtension.dll的引用,则项目无法编译,这很可能是因为Dialog ID的冲突,即您的项目使用的对话框ID与WixUIExtension中的某些标准对话框相同.dll,为对话框提供不同的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”(区分大小写)。
当用户按下按钮时,他/她将看到标准的“选择打印机”对话框,并且可以从那里进行打印。
答案 21 :(得分:5)
将可能在其自己的片段中单独修补的组件放入
制作产品安装程序和修补程序时,如果在片段中包含任何组件,则必须在该片段中包含所有组件。在构建安装程序的情况下,如果您错过任何组件引用,您将从light.exe收到链接错误。但是,当您创建补丁时,如果在片段中包含单个组件引用,则该片段中的所有更改的组件将显示在您的补丁中。
像这样:<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent1" Guid="{C28843DA-EF08-41CC-BA75-D2B99D8A1983}" DiskId="1">
<File Id="SampleFile1" Source=".\$(var.Version)f\Sample1.txt" />
</Component>
</DirectoryRef>
</Fragment>
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent2" Guid="{6CEA5599-E7B0-4D65-93AA-0F2F64402B22}" DiskId="1">
<File Id="SampleFile2" Source=".\$(var.Version)f\Sample2.txt" />
</Component>
</DirectoryRef>
</Fragment>
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent3" Guid="{4030BAC9-FAB3-426B-8D1E-DC1E2F72C2FC}" DiskId="1">
<File Id="SampleFile3" Source=".\$(var.Version)f\Sample3.txt" />
</Component>
</DirectoryRef>
</Fragment>
而不是:
<Fragment>
<DirectoryRef Id="SampleProductFolder">
<Component Id="SampleComponent1" Guid="{C28843DA-EF08-41CC-BA75-D2B99D8A1983}" DiskId="1">
<File Id="SampleFile1" Source=".\$(var.Version)\Sample1.txt" />
</Component>
<Component Id="SampleComponent2" Guid="{6CEA5599-E7B0-4D65-93AA-0F2F64402B22}" DiskId="1">
<File Id="SampleFile2" Source=".\$(var.Version)\Sample2.txt" />
</Component>
<Component Id="SampleComponent3" Guid="{4030BAC9-FAB3-426B-8D1E-DC1E2F72C2FC}" DiskId="1">
<File Id="SampleFile3" Source=".\$(var.Version)\Sample3.txt" />
</Component>
</DirectoryRef>
</Fragment>
此外,在使用WiX.chm帮助文件中的“使用Purely WiX”主题进行修补时,使用此过程生成修补程序:
torch.exe -p -xi 1.0\product.wixpdb 1.1\product.wixpdb -out patch\diff.wixmst
candle.exe patch.wxs
light.exe patch.wixobj -out patch\patch.wixmsp
pyro.exe patch\patch.wixmsp -out patch\patch.msp -t RTM patch\diff.wixmst
仅仅使用单独片段中的组件构建了1.1版本的product.wixpdb是不够的。因此,请确保在发货前正确分割您的产品。
答案 22 :(得分:5)
我们在GUI的第一个屏幕中显示产品版本(微小)。因为人们每次都选择正确的版本时会犯错误。 (并让我们的开发人员寻找年龄......)
我们已设置TFSBuild以生成具有不同环境配置的转换(.mst文件)。 (我们了解需要部署的所有环境)。
由于格兰特霍利迪的原始博客帖子已关闭,我复制其内容:
MSBuild任务,用于从XMLMarch 11 2008生成MSI转换文件
在我之前的文章中,我描述了如何使用MSI Transform(* .mst)文件将特定于环境的配置设置与通用MSI包分开。
虽然这为您的配置提供了一定程度的灵活性,但转换文件有两个缺点:
幸运的是,我们可以使用Microsoft Windows Installer对象库(c:windowssystem32msi.dll)打开MSI“数据库”并创建转换文件。
Credits再次转到Alex Shevchuk – From MSI to WiX – Part 7 - 使用Transforms自定义安装,向我们展示如何使用VbScript实现此目的。基本上我所做的就是采用Alex的例子并使用Interop.WindowsInstaller.dll我实现了一个MSBuild任务。 MSBuild任务
Download源代码&amp;示例transforms.xml(~7Kb Zipped VS2008 Solution)
答案 23 :(得分:4)
而不是ORCA使用InstEd,这是查看MSI表的好工具。它还能够通过两个包来区分 变换 - &gt;比较......
此外,还提供了具有附加功能的Plus version。但免费版也为Orca提供了一个很好的选择。
答案 24 :(得分:4)
NB此片段与 REGASM Assembly.dll / codebase
基本相同这个示例中有几件事情正在发生,所以这里是代码,之后我会解释它......
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include $(sys.CURRENTDIR)\Config.wxi?>
<?if $(var.Win64) ?>
<?define CLSIDRoots = "CLSID;Wow6432Node\CLSID"?>
<?else ?>
<?define CLSIDRoots = "CLSID"?>
<?endif?>
<!-- ASCOM Driver Assembly with related COM registrations -->
<Fragment>
<DirectoryRef Id="INSTALLLOCATION" />
</Fragment>
<Fragment>
<ComponentGroup Id="cgAscomDriver">
<Component Id="cmpAscomDriver" Directory="INSTALLLOCATION" Guid="{0267031F-991D-4D88-A748-00EC6604171E}">
<File Id="filDriverAssembly" Source="$(var.TiGra.Astronomy.AWRDriveSystem.TargetPath)" KeyPath="yes" Vital="yes" Assembly=".net" AssemblyApplication="filDriverAssembly" />
<RegistryKey Root="HKCR" Key="$(var.DriverId)" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverTypeName)"/>
<RegistryKey Key="CLSID">
<RegistryValue Type="string" Value="$(var.DriverGuid)" />
</RegistryKey>
</RegistryKey>
<?foreach CLSID in $(var.CLSIDRoots) ?>
<RegistryKey Root="HKCR" Key="$(var.CLSID)" Action="none">
<RegistryKey Key="$(var.DriverGuid)" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverTypeName)"/>
<RegistryKey Key="InprocServer32">
<RegistryValue Type="string" Value="mscoree.dll" />
<RegistryValue Type="string" Name="ThreadingModel" Value="Both"/>
<RegistryValue Type="string" Name="Class" Value="$(var.DriverTypeName)"/>
<RegistryValue Type="string" Name="Assembly" Value="!(bind.assemblyFullname.filDriverAssembly)" />
<RegistryValue Type="string" Name="RuntimeVersion" Value="v2.0.50727"/>
<RegistryValue Type="string" Name="CodeBase" Value="file:///[#filDriverAssembly]" />
<RegistryKey Key="!(bind.fileVersion.filDriverAssembly)" >
<RegistryValue Type="string" Name="Class" Value="$(var.DriverTypeName)"/>
<RegistryValue Type="string" Name="Assembly" Value="!(bind.assemblyFullname.filDriverAssembly)" />
<RegistryValue Type="string" Name="RuntimeVersion" Value="v2.0.50727"/>
<RegistryValue Type="string" Name="CodeBase" Value="file:///[#filDriverAssembly]" />
</RegistryKey>
</RegistryKey>
<RegistryKey Key="ProgId" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="$(var.DriverId)" />
</RegistryKey>
<RegistryKey Key="Implemented Categories" Action="createAndRemoveOnUninstall" >
<RegistryKey Key="{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Action="createAndRemoveOnUninstall" />
</RegistryKey>
</RegistryKey>
</RegistryKey>
<?endforeach?>
</Component>
</ComponentGroup>
</Fragment>
</Wix>
如果您想知道,这实际上是ASCOM Telescope Driver。
首先,我从上面获得了建议,并在一个单独的文件中创建了一些platforma变量,你可以看到那些分散在XML中的文件。
顶部附近的if-then-else部分处理x86 vs 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中使用某些binder变量,例如!(bind.assemblyFullname.filDriverAssembly)
来获取程序集的全名。
答案 25 :(得分:3)
设置DISABLEADVTSHORTCUTS
属性以强制安装程序中的所有通告快捷方式成为常规快捷方式,并且您不需要包含虚拟注册表项以用作键路径。
<Property Id="DISABLEADVTSHORTCUTS" Value="1"/>
我认为Windows Installer 4.0或更高版本是requirement。
答案 26 :(得分:2)
当安装不允许卸载或重新安装且不回滚时执行强制重新安装。
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)
答案 27 :(得分:2)
这是一个很好的结构,但根据我的经验,我想知道你如何解决这些问题:
一个。您的所有安装似乎都落在同一目的地。如果用户需要同时安装所有3个版本,您的流程将允许此操作。他们能否明确地告诉他们触发的每个可执行文件的哪个版本?
B中。如何处理TEST和/或TRAINING中存在但尚未在LIVE中存在的新文件?
答案 28 :(得分:2)
创建一个具有自定义操作的UI,该操作将设置变量,UI将根据自定义操作中设置的变量禁用/启用下一个按钮(或类似按钮)。
不像你想象的那么直截了当,没有太多困难,只是没有在任何地方记录!
Wix Interactions with Conditions, Properties & Custom Actions
答案 29 :(得分:2)
Here's一种帮助大型Web项目验证部署文件数量与内置到MSI(或合并模块)中的文件数量相匹配的方法。我只是针对我们的主应用程序(仍在开发中)运行自定义MSBuild任务,并且它拾取了相当多的丢失文件,主要是图像,但是一些javascript文件已经滑落到了!
这种方法(通过挂钩到WiX项目的AfterBuild目标来窥视MSI的File表)可以用于其他可以访问预期文件完整列表的应用程序类型。
答案 30 :(得分:1)
修复ProgressDlg以使其正常显示。
我已将安装程序的字体大小从8增加到10,以使字体在高分辨率显示器上更加人性化,可用。我用这个XML魔术来做到这一点:
<UI Id="MyCustomUI">
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="10" />
<TextStyle Id="WixUI_Font_Big" FaceName="Tahoma" Size="12" />
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="14" />
<TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="12" Bold="yes" />
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
</UI>
但这意味着ProgressDlg不再能正常显示。这是显示安装进度的那个,就在最后。 ActionText会被剪裁,因此不会显示g和j等字母的下延。通过在后处理Javascript中调整Progressdialog上各种控件的大小和位置来解决此问题。生成MSI后运行此脚本:
var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
// The text on the exit dialog is too close to the title. This
// step moves the text down from Y=70 to Y=90, about one line.
sql = "UPDATE `Control` SET `Control`.`Y` = '90' " +
"WHERE `Control`.`Dialog_`='ExitDialog' AND `Control`.`Control`='Description'";
view = database.OpenView(sql);
view.Execute();
view.Close();
// The progressbar is too close to the status text on the Progress dialog.
// This step moves the progressbar down from Y=115 to Y=118, about 1/3 line.
sql = "UPDATE `Control` SET `Control`.`Y` = '118' " +
"WHERE `Control`.`Dialog_`='ProgressDlg' AND `Control`.`Control`='ProgressBar'";
view = database.OpenView(sql);
view.Execute();
view.Close();
// The StatusLabel and ActionText controls are too short on the Progress dialog,
// which means the bottom of the text is cut off. This step
// increases the height from 10 to 16.
sql = "UPDATE `Control` SET `Control`.`Height` = '16' " +
"WHERE `Control`.`Dialog_`='ProgressDlg' AND `Control`.`Control`='StatusLabel'";
view = database.OpenView(sql);
view.Execute();
view.Close();
sql = "UPDATE `Control` SET `Control`.`Height` = '16' " +
"WHERE `Control`.`Dialog_`='ProgressDlg' AND `Control`.`Control`='ActionText'";
view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();