Friday, 18 January 2008

Altering ASP.NET web.config during WIX MSI installation

Here's an example of how to amend a web.config file during an ASP.NET website setup via a wix MSI. I needed to do this for a project recently, and couldn't find any examples at the time. In wix3 you can use the XmlConfig element from the wixUtilExtension.dll, but the syntax varies quite a lot depending on exactly what you're trying to achieve, so other examples I found for other kinds of config file changes didn't help as much as I'd hoped. Anyway, here's the example.

The purpose of this change is to:

  • Make sure debug file generation is switched off after installation (it may have been switched on during development).

  • Add a reference to an assembly in the global assembly cache (GAC) (and the version number changes each deployment as this is a major upgrade).

The hardest part is the fact that I want to add a new element to a list (an
<add> element to the existing <assemblies> list, and then set an attribute on the newly added element. That involves two steps in wix one to add the element, the other to set the attribute on it), and the second step needs to work against the exact element just added. Sometimes that's easy if the element you just added has a unique key. I guess if I knew more xpath I'd be able to work out a query that would identify the new element as it doesn't already have an attribute. However, there seemed to be a new feature in wix3 where the XmlConfig element can be nested within another one, and also where instead of an xpath expression for the element you're changing an attribute on, you can use the Id of the XmlConfig node for the parenent element. (I may be wrong, but this only seemed to work if you used the nesting, i.e. you can't have all your XmlConfig nodes at the same level and expect one to be able to reference another).

Here's how the web.config file is when it gets built into the MSI (and how it would get deployed if we didn't amend it) (irrelevent bits skipped):

<configuration>
<system.web>
<compilation debug="true">
<assemblies>
<add
assembly="System.Core, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="System.Web.Extensions, Version=3.6.0.0, Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>

<add
assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

</assemblies>
</compilation>
</system.web>
</configuration>

And here's how we really want it to be after deployment:

<configuration>
<system.web>
<compilation debug="false">
<assemblies>
<add
assembly="System.Core, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="System.Web.Extensions, Version=3.6.0.0, Culture=neutral,
PublicKeyToken=31BF3856AD364E35"/>

<add
assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>

<add
assembly="MyGacAssembly, Version=1.0.1003.0, Culture=neutral,
PublicKeyToken=f00a07e3b937d388"/>

</assemblies>
</compilation>
</system.web>
</configuration>

The relevent wix XML to do the job is as follows:

<Component Id="MyGacAssembly.dll" Guid="...">
<File Name="MyGacAssembly.dll"
Source="$(var.MyAssemblyProject.TargetDir)" Assembly=".net" KeyPath="yes" />

</ComponentId>
<Component Id="WebConfigFile" Guid="..." KeyPath="yes">
<File Name="web.config"
Source="$(var.MyWebProject.ProjectDir)" />

<util:XmlConfig Sequence="1"
Id="SwitchOffDebug" File="[WebFolder]\web.config" Action="create" On="install"
Node="value" ElementPath="/configuration/system.web/compilation" Name="debug"
Value="false" />

<util:XmlConfig Sequence="2"
Id="NewAssemblyElement" File="[WebFolder]\web.config" Action="create"
On="install" Node="element"
ElementPath="/configuration/system.web/compilation/assemblies" Name="add">

<util:XmlConfig Sequence="3"
Id="NewAssemblyAttribute" File="[WebFolder]\web.config"
ElementPath="NewAssemblyElement" Name="assembly"
Value="!(bind.assemblyFullName.MyGacAssembly.dll)" />

</util:XmlConfig>
</Component>

Other notes...

  • The util: prefix comes from adding the wixUtilExtension.dll as a reference and adding its namespace to the wix file.

  • The !(bind.assemblyFullName...) syntax tells wix to replace this pattern with the fully qualified name of the assembly referred to by its file ID (name). You can also use things like !(bind.assemblyVersion...) etc if you need to get other info. But you can only apply this to files which have their Assembly=".net" attribute set. Normally that means they're assemblies that go into the GAC, but you can prevent them being GAC'ed by using the AssemblyApplication parameter on the File node to refer to the directory where the assembly will reside instead.

No comments: