Thursday, February 26, 2009

WIX in Distributed Build Enviornments

I am trying to get WIX integrated into our distributed continuous integration and build system.  I thought it would be simple, I was wrong.

WIX wants you to install it.  Being a program that creates installs that makes sense.  But in its .target files that let you integrate it into MSBuild it makes assumptions about where things will be based on registry keys it sets during the install.  This presents a problem.

We have a build farm, around 20 machine, we have a 50 developers, most with two machines.  Problem number 1: I don't want to co-ordinate getting a program rolled out across 100+ machines.  Problem number 2: upgrading.  I hope over time there will be updates to WIX.  We version our code every day.  When I upgrade to a new version of WIX I don't want to have to worry that if someone tries to build an older version of code it might not work with a newer version of the code.  I want to version the WIX environment right along side our code.  Can't do that with something I need to install.

So I figure I can just toss the WIX components into a folder in source control and update some paths in the .target files and everything will be fine.  Turns out that's a bit optimistic.

Lots of things in the WIX target files need to know where WIX is installed to.  Only problem is the build pulls the source down into dynamical generated temp files.  But I figured I could just use relative paths and things would be fine.

Not so much.  Right now I have just two project that need to reference WIX stuff, the actual .wixproj and the custom action project.  The custom action project is in a sub-folder of .wixproj file.  So if I change:

<usingtask taskname="HeatFile" assemblyfile="$(WixToolPath)WixUtilExtension.dll">
</usingtask>
to:
<usingtask taskname="HeatFile" assemblyfile="..\..\External\WIX\WixUtilExtension.dll">
</usingtask>

The relative path will only work for one of the projects and not the other one because the relative paths get resolved from different starting points are different.  (As a side note, using a macro that references a solution specific directory wouldn't work for the same reason, we has solutions at diffrent levels of the folder hierarchy as well.)

(So at this point I though about just changing my project directory structure so that all the projects that needed WIX would be at the same level.  But I figured that that would come back and bite me some day.)

The first solution that came to mind was that if I could find a way to specify the paths as relative to the .target file I would be all set.  And indeed I found a way to do this, but it requires a custom build task and so you end up with the same problem.

On to the final solution.  What I ended up doing was to have each project that needed to use WIX set a well known property that has the relative path from that project to the WIX files in it.

So you have to add the following to the project files that want to access WIX:

<wixpath condition=" '$(WixPath)' == '' ">
$(MSBuildprojectDirectory)\..\..\..\External\WIX\
</WixPath>


<wixcatargetspath condition=" '$(WixCATargetsPath)' == '' ">

$(WixPath)Wix.CA.targets</WixCATargetsPath>


and then in your .target files just base all the paths off of this the $(WixPath) property.  Attached are the WIX .target files modified to work this way.

Hopefully this helps you on your WIX development journey.

Wednesday, February 18, 2009

Installing Directory Structres in WIX

I've been doing some WIX work lately and so far I have been pretty happy with it so far.  But one thing has been bugging me.  I want to install a directory structure of content files (in this case our help file collection) but I don't want to have a hard coded list of files.

I get that its not a best practice to do it this way.  But I don't really care about patching or upgrading.  Our company has made a business decision that we only do full installs, we don't support patching or upgrading, only uninstall/re-install. I just want to put one statement into my install and have a whole directory show up in my install.  Should be easy but its not.

So the only thing to do is to test out the extensibility model of WIX.  Turns out to be pretty easy to extend.  3 classes, 110 lines of code, and 1 XSD file was all that was needed.

The full project is here.  Just compile it and then reference the dll in your install project.  Then just add a FileGroup element into your XML. Then specify four attributes:
  • Source : The path to the directory to copy.
  • DirectoryRoot: The WIX directory ID that the root of the copy should be installed to.
  • DirectoryName: The the directory should be called on the target machine.
  • ComponentGroupID: This is the ID for the final ComponentGroup that is created, you can refrence this in your features.
Hopefully its pretty easy to use.  One improvement I think I might make soon is to add an excluding rule to it.  Should be pretty easy to do if though.


Tuesday, February 3, 2009

The Problem with ICollection

We'll start with an Innocent little toy class:

    public class Remover

    {

        public void Remove(ICollection<T>collection, T item)

        {

            collection.Remove(item);

        }


    }


What could possible go wrong with this?  According to MSDN it will return true if succeeds, false if it fails and throw a NotImplimented exception if the collection is read only.

So what would you expect this function to do:

        public void TryToRemove()

        {

            int[] ints = new[] { 3, 4, 5, 6, 7 };

            Remover remover = new Remover();

            remover.Remove(ints, 3);

        }


Maybe it does nothing, the collection isn't really readonly so the return call should probably return false.  Maybe it throws an exception, but, and here is the crux of my point, I would expect some kind of warning to displayed if that was the case.

Indeed, it does throw an exception: "Test method CollectionExample.UnitTest1.TryToRemove threw exception:  System.NotSupportedException: Collection was of a fixed size.."

(The .. is verbatim from the error message)

I compiled with warnings as errors and used Style Cop and Code Analysis, you'd think one of those tools would give you a warning that this implicit cast you are doing is going to cause you problems.  Or how about this Microsoft, why even make an array inherit from, or have an implicit cast to ICollection if you are just going to provide a degenerate implementation.  Just having it implement IEnumerable seems like it would have been a better choice.

That's my end rant, just one more thing to keep in mind when pushing the bits around.