WiX: A not-quite-so-gentle introduction

Previously I gave a high-level introduction to the WiX and MSI technologies.  I'm going to dig a little bit deeper here and get a bit closer to the nuts of bolts of these technologies.  Note that I'm not intending to give you an introduction to the details of writing WiX installers, the tutorial I linked to previously (which is also linked in from the main WiX site) does that quite well enough.  I do think that there is some context you will need before you start that tutorial and that's what I'm aiming to give you here.

Features and Components

We are all used to seeing screens like this during an installation:

feature-choice

These are features, in MSI (and WiX) terminology.  A feature consists of a number of components and very little else.  Components can be used by more than one feature.

A component is uniquely identified by a GUID and consists of a number of resources that get installed (or uninstalled) together.  The list of resources that Windows Installer can deal with natively is:

Files and folders Registry keys   Environment variables
Entries in an INI file ODBC drivers and data sources   Windows services
Shortcuts Assemblies  

This is clearly a limited list - missing resource types include X.509 certificates, web sites, SQL databases, etc, etc.  Fortunately Windows Installer offers an extensibility mechanism.

Custom Actions

Custom actions are the way that Windows Installer let's MSIs do things other than just installing the above resource types.  There are a variety of types of custom actions and these all need to be explicitly ordered and scheduled by the MSI developer.  The main custom actions that I've used consist of running an executable, running some code in-process (i.e. via a DLL which is embedded in the MSI) and do some manipulation of properties.

Somebody authoring an MSI package is most likely using some environment or other (e.g. WiX) and this environment will typically provide some custom actions.  Cleverly, some of these custom actions look like additional resource types to the MSI developer.  For instance, WiX itself provides facilities to install and uninstall the following:

COM and DCOM components X.509 certificates Device drivers
Event logs File shares SQL databases
SQL scripts Users and groups IIS web sites
IIS virtual directories Entries in XML files Visual Studio help files
COM+ applications MSMQ queues  

Custom actions are not intrinsically linked to components or features.  This means that you don't have a convenient event to hook into so that, say, you can run a particular executable once some files have been installed.  Instead, all custom actions can have conditions attached to them which detail whether or not the custom action should execute.

So if you want an executable to run once some files have been copied, you need to set a condition on the custom action which runs the executable.  This condition would ensure that the custom action executed only when the particular component (or feature) containing the files was being installed.  You would also need to schedule the custom action so that it occurred after the installation of the files.

Note that it's rare that you will author just one custom action to modify system state.  Once you have done this, you need to give some thought to rolling back the action if the install fails, as well as uninstalling the action (strictly speaking you should also worry about rolling back the uninstall!).  So you'll usually write them in pairs or triples.

Properties

Properties are just variables within your MSI package.  They can have a default value or a value can be supplied on the command line by the user.  Initial values can also be obtained by scanning the file system (e.g. to look for a particular file or directory), by using environment variables or by scanning the registry.  You can also build up property values from other properties, but you need to use a custom action to do this.

November 15 2006

WiX: A gentle introduction

Currently I'm doing work using the WiX toolset.  There doesn't seem to be a lot of documentation around the web, so I thought I post my various travails in getting it to do what I want.  But first an introduction to it and some links to useful resources:

What is WiX?

WiX stands for Windows Installer XML (don't ask me why they use a small i).  It was the first open source project released by Microsoft.  The main site is at http://wix.sourceforge.net/ and to quote from that:

The Windows Installer XML (WiX) is a toolset that builds Windows installation packages from XML source code.

So it's basically an open source way of building MSI files.

What's an MSI file?

MSI stands for Microsoft Installer (they used a big i this time), but the question really isn't as silly as you may think.  You see, an MSI file is not a program.  That really is worth repeating.  An MSI file is not an executable, it is a database.  It has tables with particular schemas (as well as your own custom tables) and Windows Installer uses the information in these tables to drive the installation of an application.  The list of MSI tables can be found at http://msdn.microsoft.com/library/en-us/msi/setup/database_tables.asp and if you're very interested you can download the Windows Platform SDK, get a tool called Orca  (part of the Windows Installer SDK).  This allows you to view and edit the contents of any MSI file and it looks like this:

 orca

It's worth stressing that this database includes all the dialog windows that drive the installation process, the files for the actual installation, instructions on what to put where and many other things. 

So what does WiX do then?

In a nutshell, WiX converts XML files into MSI databases.

So the XML must be quite complicated then ...

Well I don't find it very intuitive.  Things are getting easier as the WiX toolset matures (version 3.0 includes some quite nifty Visual Studio integration, for example).  There is also the WixEdit project on Sourceforge, which offers GUI screens for editing the WiX XML file - I discovered this a bit too late to use it for my current work, but it looks worthwhile.

Where can I find more information?

Try the following:

November 10 2006

Using WCF in the MessengerService

Recently I posted an article and some code for a service which logs onto MSN Messenger and exposes some web services for sending messages.  I promised in that article that if I got time to refactor the code to use WCF I would post it - here it is and the code is quite a lot simpler.  The revised code is attached to this blog post, you can click here to download the code and binaries.

Removing the Web Services Code

There was previously some complexity in hosting the web services and ensuring that they were functioning correctly.  This complexity manifested itself in the form of a background Thread to service requests, a ManualResetEvent for synchronisation, two AppDomains at runtime and a few helper classes.  Since we are no longer using web services, all of this can be deleted.

Creating a Service Contract

Often some thought is need around the exact operations to be exposed and their various signatures.  However, since I wasn't aiming to change functionality at all, I didn't see any point in revisiting the operation signatures we had previously.  So all that was involved in creating the service contract was setting up an IMessengerService interface and decorating it with the appropriate attributes:

[ServiceContract]
public interface IMessengerService
{
    [
OperationContract]
    bool QueueMessage(string[] recipients, string message);

    [
OperationContract]
    bool QueueMessageToOnePerson(string recipient, string message);

    [
OperationContract]
    bool QueueMessageToOnePersonWithValidity(string recipient, string message, int validityInMinutes);

    [
OperationContract]
    bool QueueMessageWithValidity(string[] recipients, string message, int validityInMinutes);
}

Hosting the WCF Service

The old web service methods were thin wrappers around calls to QueueManager methods.  These wrapper methods now live in the QueueManager class which implements IMessengerService.  So all that's needed to expose this singleton instance over WCF is the following code:

_host = new ServiceHost(QueueManager.Instance);
_host.Open();

and an attribute on the QueueManager class as follows:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class QueueManager : IMessengerService
{
    // Details of the class omitted here
}

Reworking the MSBuild Client

Strangely enough, while the move to WCF was a simplification of the service code there is now more code in the MSBuild client.  This is due to one particular quirk of code running as an MSBuild task - there is no config file.  WCF is (rightly so) tailored to expressing service endpoints in config files.

As a result of this, the MsnNotification task has to programmatically construct the full endpoint (including binding, service behaviours, etc) based on parameters passed in from the build script.  To avoid excessive complexity, the code deduces the transport protocol from the service URL and then uses the default binding.

Binding binding = null;
EndpointAddress address = new EndpointAddress(_url);
switch (address.Uri.Scheme)
{
    case "http":
    case "https":
        binding =
new BasicHttpBinding();
        break;
    case "net.tcp":
        binding =
new NetTcpBinding();
        break;
    case "net.msmq":
        binding =
new NetMsmqBinding();
        break;
    case "net.pipe":
        binding =
new NetNamedPipeBinding();
        break;
    case "net.p2p":
        binding =
new NetPeerTcpBinding();
        break;
    default:
        Log.LogError(
"Unable to deduce correct binding from URL. Supported schemes are http, https, net.tcp, net.msmq, net.pipe and net.p2p");
        return false;
}

ChannelFactory
<MessengerSvc.IMessengerServiceChannel> factory = new ChannelFactory<MessengerSvc.IMessengerServiceChannel>(binding, address);

MessengerSvc.
IMessengerServiceChannel channel = factory.CreateChannel();
channel.Open();

// Use the service proxy as before

Benefits of Using WCF

So aside from the use of technology, have we gained anything from using WCF instead of the web services?  Sure we have.  Here's the list as I see it:

  • Reduced code complexity - a lot of the complex stuff around the hosting of the web services is now taken care of by the System.ServiceModel.ServiceHost class.
  • Better scalability - the sharp-eyed reader will notice that previously we only had a single thread to process incoming requests.  The ServiceHost class does a better job of servicing clients.
  • A wider range of protocols - we are no longer limited to plain SOAP messages.  We can declaratively add in things like security, transactions, reliable messaging, etc, etc (although the MSBuild task doesn't support these right now).  Additionally, we can expose the service over MSMQ, TCP sockets, etc, etc.

UPDATE

Howard took one look at my code and immediately started refactoring it.  The updated version is attached to this post.  The main change he made was to improve the access to the configuration file - creating a class that derives from ConfigurationSection is much more of a .Net 2.0 way of doing things.

November 7 2006

Reflection on Generic Types (part 2)

Some time ago I posted a question about reflection on generics.  This remained unanswered until Anthony recently posted the solution in a comment.  To reiterate what was said in there:

The Question

I have a generic interface ICustomDataMapper<T>.  I want to know, programatically, if a Type t which has been passed to my method implements this interface, or at least a closed version thereof.  The best I could come up with was:

bool DoesImplement = false;
foreach (Type intface in t.GetInterfaces())
{
     if (intface.IsGenericType &&
        intface.GetGenericTypeDefinition().Name.StartsWith
              
("ICustomDataMapper"))
     DoesImplement =
true;
}

This is a bit rubbish.  Can anyone show me a better way?

Anthony's Solution

Anthony suggested instantiating a concrete version of the generic type to relfect over it.  This doesn't work too well with interfaces, but a simple modification gets it to work as follows:

bool DoesImplement = false;
Type GenericBase = typeof(ICustomDataMapper<string>).GetGenericTypeDefinition();
foreach (Type intface in t.GetInterfaces())
{
     if (intface.IsGenericType &&
        intface.GetGenericTypeDefinition() == GenericBase)

     DoesImplement =
true;
}

Yet Another Way

This prompted some further investigation and I found out that there are two other ways to access the generic type:

Type GenericBase = Type.GetType("MyProject.DataMapping.ICustomDataMapper`1");

Although it works, this is back to being a bit rubbish because the type name is coded in a string so there is no compile-time checking.  Also we need to use the fully qualified type name.  However, the following also works and gets around these problems:

Type GenericBase = typeof( ICustomDataMapper<> );

I think that this has to be the neatest solution. 

An Unrelated Aside

As per Anthony's comment, the number after the backtick ( ` ) relates to the number of generic type parameters on the definition.  This means that a generic type with two parameters is completely unrelated to one with only one, even if they share the same name.  Or to put it another way, the following two definitions can be in the same namespace:

public interface IGeneric<T>
{
    T DoSomething(T t);
}

public interface IGeneric<K, V>
{
    K DoSomethingElse(V v);
}

A type can then implement both of these interfaces, as in:

public class Concrete: IGeneric<Concrete>, IGeneric<string, Concrete>

November 2 2006
Newer Posts Older Posts