创建不带 Votive 的托管代码编写的 WIX 自定义操作






4.91/5 (35投票s)
创建不带 Votive 的托管代码编写的 WIX 自定义操作。
目录
引言
在本文中,我将介绍一个完整的示例,演示如何为使用 C# 编写的 WIX 安装程序创建自定义操作。本文不介绍 Votive,因此该示例适用于所有版本的 Visual Studio,包括 Express 版本。我还将介绍如何将自定义操作集成到安装程序中,以及构建所有内容的构建脚本。
关于 Votive 的说明
"Votive 是什么" 可以在 此处找到。尽管 Votive 是一个非常好的插件,但在尝试使用它时,您会发现很多限制。不幸的是,Votive 目前仅适用于 VS 2005/2008,并且因为它是一个插件,所以在 Express 版本中无法使用。我很久以前就放弃了这个插件,因此本文中也没有介绍它。
本文也旨在说明如何在没有 Votive 的情况下创建自定义操作。
实现
实现本身并不难。但是,有一些棘手的步骤需要完成才能达到目标。
我创建了一个名为 MyCustomAction
的库。您可以在源代码中看到它。该库包含一个具有单个 static
方法的类。代码可以在清单 1 中看到。
[CustomAction]
public static ActionResult MySimpleAction(Session session)
{
try
{
File.AppendAllText(@"c:\tmp\time.txt", ";
Installation: " + DateTime.Now.ToString());
}
catch (Exception)
{
return ActionResult.Failure;
}
return ActionResult.Success;
}
如您所见,代码中没有复杂之处。重要的是使用 CustomAction
属性标记方法,返回 ActionResult
并期望 Session
。为了使这些类型可用,您需要引用库 Microsoft.Deployment.WindowsInstaller.dll。此库位于 %ProgramFiles%\Windows Installer XML v3\SDK。在属性中将其设置为 Copy Local True。然后,您可以根据需要将您的 C# 自定义代码写入此函数。作为测试,我准备了一些简单的代码,只是将时间戳写入 C 盘 tmp 目录下的名为 time.txt 的文件中。
假设您的 C# 代码已完成,您可以修改构建过程以创建 WIX 可接受的程序集。为此,请遵循以下步骤:
-
右键单击项目,然后选择“卸载项目”。
图 1 - 卸载项目 -
右键单击已卸载的项目,然后选择“编辑”。
图 2 - 编辑项目 -
在第一个
PropertyGroup
元素中,将TargetFrameworkVersion
更改为您最方便的版本。这是 .NET Framework 的版本。目前,最高可能版本是 v3.5。但如果您使用的 WIX 版本高于我,情况可能会有所不同。我使用的是 WIX 3.0。 -
在第一个
PropertyGroup
元素中,添加清单 2 中的元素。这是 WIX 目标路径,其中定义了用于创建最终版本 DLL 的后置构建操作,该 DLL 可被 WIX 接受。<WixCATargetsPath Condition=" '$(WixCATargetsPath)' == '' "> $(MSBuildExtensionsPath)\Microsoft\WiX\v3.0\Wix.CA.targets</WixCATargetsPath>
清单 2 – WixCATargetsPath 变量的声明。 -
在文件末尾,关闭
Project
标签之前,添加以下import
元素:<Import Project="$(WixCATargetsPath)" />
清单 3 – 导入 WIX CA 目标。这将确保目标被加载。
-
在
ItemGroup
元素中,找到Include="Microsoft.Deployment.WindowsInstaller"
的Reference
元素。作为一个子元素,放置<Private>True</Private>
,以确保程序集始终被复制到 bin 文件夹。 -
关闭文件,右键单击项目 ->
ReloadProject
。图 3 - 重新加载项目
更改的结果应与清单 10 类似。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B7AF9993-2C2E-4C03-98F2-109A8B6557BE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MyCustomAction</RootNamespace>
<AssemblyName>MyCustomAction</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<WixCATargetsPath Condition=" '$(WixCATargetsPath)' == '' ">
$(MSBuildExtensionsPath)\Microsoft\WiX\v3.0\Wix.CA.targets</WixCATargetsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Deployment.WindowsInstaller,
Version=3.0.0.0, Culture=neutral, PublicKeyToken=ce35f76fcda82bad,
processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\Program Files\
Windows Installer XML v3\SDK\Microsoft.Deployment.WindowsInstaller.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="SimpleCustomAction.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(WixCATargetsPath)" />
<!-- To modify your build process, add your task inside one of the targets
below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
好了,您可以使用 Visual Studio 或直接使用 msbuild
来构建项目。如果失败,则意味着您在上述步骤中有地方出错。如果成功,请查看 bin 目录。您应该看到 3 个程序集。我们期望看到的最重要的程序集是名称为 your_assembly.CA.dll 的程序集。
这个程序集实际上不是 .NET 托管程序集,而是由 WIX 目标在后置构建操作中创建的非托管程序集(在本例中是 C++)。这个程序集公开了一个具有您的 static
函数名称的 DLL 入口点。您可以使用例如 http://www.dependencywalker.com/ 来发现它。至此,您用 C# 编写的自定义操作就完成了。在本文的后续部分,我将描述如何将自定义操作集成到安装程序中。
向安装程序添加自定义操作
我打算将本章分为两部分。第一部分仅显示自定义操作的简单执行。第二部分显示了将一些参数从 WIX 传递到 C# 的示例。
向安装程序添加虚拟自定义操作
WIX 有几种自定义操作调用类型。我们需要 Type 1
:调用动态链接库中的函数。这包括 3 个步骤:
- 添加 DLL 链接。
<Binary Id="myAction" SourceFile="..\MyCustomAction\bin\Release\MyCustomAction.CA.dll" />
清单 4 – 添加库链接。 - 指定自定义操作。
<CustomAction Id="myActionId" BinaryKey="myAction" DllEntry="MySimpleAction" Execute="deferred" Return="check" />
清单 5 – 指定自定义操作。 - 指定自定义操作应在安装的哪个步骤执行。
<InstallExecuteSequence> <Custom Action="myActionId" Before="InstallFinalize" /> </InstallExecuteSequence>
清单 6 – 调用自定义操作。
整个代码可以在清单 7 中看到。
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="{C98462B7-1458-4199-AEA0-2066C3F4D2D1}"
Name="WixCustomActionTest.Setup"
Language="1033"
Version="1.0.0.0"
Manufacturer="WixCustomAction.Setup"
UpgradeCode="{15F2F252-9FCD-4C3B-AC29-070C624610B9}">
<Package InstallerVersion="200" Compressed="yes" />
<Property Id="ALLUSERS" Value="1" />
<Binary Id="myAction"
SourceFile="..\MyCustomAction\bin\Release\MyCustomAction.CA.dll" />
<Media Id="1" Cabinet="MyWeb.cab" EmbedCab="yes" />
<!-- Configurable install location -->
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
<!-- Creating directories -->
<Directory Id="TARGETDIR" Name="SourceDir">
<!-- Install stuff into program files folder. -->
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION"
Name="WixCustomAction">
<Component Id="SomeContent" Guid="">
<!-- This file is here just
for having some content
in the installer. -->
<File Id="BuildFile" KeyPath="yes"
Source="Build.build" Vital="yes" />
</Component>
</Directory>
</Directory>
</Directory>
<!-- Complete feature which will be installed. -->
<Feature Id="Complete"
Title="WixCustomAction - WixCustomAction awesome test"
Level="1"
Display="expand">
<ComponentRef Id="SomeContent" />
</Feature>
<CustomAction Id="myActionId"
BinaryKey="myAction"
DllEntry="MySimpleAction"
Execute="deferred"
Return="check" />
<InstallExecuteSequence>
<Custom Action="myActionId" Before="InstallFinalize" />
</InstallExecuteSequence>
<UIRef Id="WixUI_Minimal" />
</Product>
</Wix>
在自定义操作调用中使用条件
自定义操作中的条件作为内部文本写入 Custom 属性。如果条件为 true
,则执行操作,反之亦然。条件可以使用 AND、OR 运算符与其他条件组合。作为条件的输入是 properties
和/或 static
值。自定义操作中的条件在大多数情况下仅由 properties
组成。您可以在 [2] 中找到这些 properties
,特别是在“安装状态属性”部分。因此,如果您想仅在应用程序执行安装时调用清单 6 中的自定义操作,则按清单 10 指定条件。
<Custom Action="myActionId" Before="InstallFinalize">NOT Installed</Custom>
为了更清楚地说明这一点,我将介绍一个表格,解释 properties
在不同阶段包含的值。我从 [3] 中获取的。
属性名称 | Install | Uninstall | 修复 | 修改 | 升级 | 文档链接 |
已安装 | 假 | True | 假 | True | True | 显示 |
REINSTALL | True | 假 | True | 假 | 假 | 显示 |
UPGRADINGPRODUCTCODE | True | 假 | True | True | True | 显示 |
通过输入参数扩展的示例
编写自定义操作时,通常需要将一些参数从 WIX 传递到 C#。这可以通过 session
参数,特别是 CustomActionDataCollection
来完成。有关更多信息,请查看清单 11 中的代码。这只是一个简单的“Hello world”示例,用于显示从 WIX 传递到 C# 的值。
[CustomAction]
public static ActionResult MySimpleAction(Session session)
{
try
{
session.Message(InstallMessage.Warning,
new Record(new string[]
{
string.Format("INSTALLLOCATION{0}",
session.CustomActionData["INSTALLLOCATION"])
}));
session.Message(InstallMessage.Warning,
new Record(new string[]
{
string.Format("Another Value{0}",
session.CustomActionData["AnotherValue"])
}));
}
catch (Exception exception)
{
session.Log(exception.ToString());
return ActionResult.Failure;
}
return ActionResult.Success;
}
但是,这并不那么简单。还需要另一个自定义操作(Type 51)来将 WIX 属性设置为 WIX 端上的 CustomActionDataCollection
。您可以在清单 12 中看到如何操作。
<CustomAction Id="SetCustomActionDataValue" Return="check"
Property="myActionId" Value="INSTALLLOCATION=[INSTALLLOCATION];
AnotherValue='Just a value'" />
您还必须指定何时执行此操作。最合理的时间是在您的 C# 自定义操作之前,可以通过清单 14 中的代码来实现。
<InstallExecuteSequence>
<Custom Action="SetCustomActionDataValue" Before="myActionId">NOT Installed</Custom>
<Custom Action="myActionId" Before="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
与之前的示例一样,最好介绍整个 WIX 代码,以便看到与清单 7 相比的差异。
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="{C98462B7-1458-4199-AEA0-2066C3F4D2D1}"
Name="WixCustomActionTest.Setup"
Language="1033"
Version="1.0.0.0"
Manufacturer="WixCustomAction.Setup"
UpgradeCode="{15F2F252-9FCD-4C3B-AC29-070C624610B9}">
<Package InstallerVersion="200" Compressed="yes" />
<Property Id="ALLUSERS" Value="1" />
<Binary Id="myAction"
SourceFile="..\MyCustomAction\bin\Release\MyCustomAction.CA.dll" />
<Media Id="1" Cabinet="MyWeb.cab" EmbedCab="yes" />
<!-- Configurable install location -->
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
<!-- Creating directories -->
<Directory Id="TARGETDIR" Name="SourceDir">
<!-- Install stuff into program files folder. -->
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION"
Name="WixCustomAction">
<Component Id="SomeContent" Guid="">
<!-- This file is here just for
having some content
in the installer. -->
<File Id="BuildFile"
KeyPath="yes" Source="
Build.build" Vital="yes" />
</Component>
</Directory>
</Directory>
</Directory>
<!-- Complete feature which will be installed. -->
<Feature Id="Complete"
Title="WixCustomAction - WixCustomAction awesome test"
Level="1"
Display="expand">
<ComponentRef Id="SomeContent" />
</Feature>
<CustomAction Id="myActionId"
BinaryKey="myAction"
DllEntry="MySimpleAction"
Execute="deferred"
Return="check" />
<CustomAction Id="SetCustomActionDataValue" Return="check"
Property="myActionId" Value="
INSTALLLOCATION=[INSTALLLOCATION];AnotherValue='Just a value'" />
<InstallExecuteSequence>
<Custom Action="SetCustomActionDataValue"
Before="myActionId">NOT Installed</Custom>
<Custom Action="myActionId" Before="
InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
<UIRef Id="WixUI_Minimal" />
</Product>
</Wix>
构建安装程序
正如您所注意到的,Product.wxs 是独立的,没有与 Visual Studio 解决方案集成。要构建安装程序,我们需要创建一个构建脚本。基本上,我们需要两个命令来完成此操作:调用 candle.exe,然后调用 light.exe。为此,我创建了一个名为 CreateInstaller
的目标,可以在清单 8 中看到。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Define some variables -->
<PropertyGroup>
<MsiOut>bin\Release\CustomActionTest.msi</MsiOut>
<ProjectName>WixCustomAction</ProjectName>
</PropertyGroup>
<!-- The list of WIX input files -->
<ItemGroup>
<WixCode Include="Product.wxs" />
</ItemGroup>
<!-- The list of WIX after candle files -->
<ItemGroup>
<WixObject Include="Product.wixobj" />
</ItemGroup>
<!-- Define default target with name 'Build' -->
<Target Name="Build">
<!-- Compile whole solution in release mode -->
<MSBuild
Projects="..\WixCustomActionExample.sln"
Targets="Build"
Properties="Configuration=Release" />
</Target>
<!-- Define creating installer in another target -->
<Target Name="CreateInstaller">
<!-- At last create an installer -->
<Exec
Command='"$(WIX)bin\candle" @(WixCode, ' ')'
ContinueOnError="false"
WorkingDirectory="." />
<Exec
Command='"$(WIX)bin\light" -ext WixUIExtension -out
$(MsiOut) @(WixObject, ' ')'
ContinueOnError="false"
WorkingDirectory="." />
<!-- A message at the end -->
<Message Text="Install package has been created." />
</Target>
</Project>
清单 8 中的构建脚本还包含用于以发布模式构建解决方案的目标。
打开 Visual Studio 命令提示符或 cmd(如果 msbuild.exe 在那里可用)。转到构建脚本所在的文件夹,然后执行清单 9 中的命令。
Msbuild /t:Build;CreateInstaller Build.build
如果一切顺利,您应该在 bin 文件夹中找到 msi 包。
结论
使用托管代码编写自定义操作是向安装程序添加自定义活动的非常方便的方式。理解其工作原理对于能够发现恶意错误非常重要。我希望本文能为您带来一些启示,对您有所帮助。请随时分享您的想法和投票 ;)。感谢您的阅读。
参考文献
- [1] ISBN-10: 1849513724, ISBN-13: 978-1849513722, WIX: A Developer’s Guide to Windows Installer XML, Nick Ramirez
- [2] http://msdn.microsoft.com/en-us/library/aa370905(VS.85).aspx
- [3] http://stackoverflow.com/questions/320921/how-to-add-a-wix-custom-action-that-happens-only-on-uninstall-via-msi/731700#731700
历史
- 2010 年 12 月 02 日:首次发布
- 2011 年 01 月 28 日:添加了从 WIX 向 C# 自定义操作传递数据的示例