
One of the very cool features of modular application in my opinion is the possibility to install features when they are actually required by the application. Microsoft uses this since Office 2000 to provide the user with the possibility to install a module on demand when the module is marked install on first use.
To make on demand installation of modules possible in a CompositeWPF application I created an extended version module loader service. In this post I will show how you can integrate Windows installer functionality and build the module loader service yourself.
Integrating Windows Installer
One of the key parts of installing modules on demand is creating a way for a CompositeWPF application to talk to Windows Installer. This involves some win32 hacking by importing functions from msi.dll into the application.
Windows Installer provides a way to query the status of a feature by calling MsiQueryFeatureState providing the productcode and the name of the feature you want to know the status of. The result of this method is a value in the InstallState enumeration where Local or Default indicates that the feature is actually installed and Absent that the feature is not available at this moment. Microsoft uses Advertised to indicate that a feature has to be installed on first use and I did so too. The DLL import required here looks like this:
/// <summary>
/// Retrieves the install state of a specific feature
/// </summary>
/// <param name="productCode">Product code of the product
/// <param name="featureName">Name of the feature
/// <returns>Returns the install state of the product
[DllImport("msi.dll",EntryPoint="MsiQueryFeatureState",CharSet=CharSet.Unicode)]
The second functionality required is a way to actually install a single feature. This is done by invoking MsiConfigureFeature providing the productcode, the name of the feature you want to configure and the target install state. Although you can specify quite a lot install state values here I recommend to use InstallState.Local to get the feature installed. This will install the feature for the local user. The DLL import required for this looks like this:
/// <summary>
/// Configures a feature of the specified application
/// </summary>
/// <param name="productCode">Product code of the application</param>
/// <param name="featureName">Name of the feature to configure</param>
/// <param name="state">Install state to configure</param>
/// <remarks>
/// The following install states can be configured:
/// <see cref="InstallState.Local"/> Feature will be installed locally
/// <see cref="InstallState.Absent"/> Feature will be uninstalled
/// <see cref="InstallState.Source"/> Feature will run from source
/// <see cref="InstallState.Default"/> Feature will be installed to default location
/// </remarks>
/// <returns></returns>
[DllImport("msi.dll", EntryPoint = "MsiConfigureFeature", CharSet = CharSet.Unicode)]
public extern static uint ConfigureFeature(string productCode,
string featureName,
InstallState state);
Building the extended module loader service
Once you have the windows installer integration working it’s quite simple to create a custom service that ties the windows installer features and the CompositeWPF features for loading modules on demand together.
The functionality to load the custom module looks like this:
/// <summary>
/// Loads a single module and installs it if it is
/// installed and the installIfNotAvailable parameter is set to true
///</summary>
///<param name="moduleName">Name of the module</param>
/// <param name="installIfNotAvailable">Whether to install the module if available</param>
public void LoadModule(string moduleName, bool installIfNotAvailable)
{
if(String.IsNullOrEmpty(moduleName))
throw new ArgumentNullException("moduleName");
IModuleEnumerator moduleEnumerator = _container.Resolve<IModuleEnumerator>();
ModuleLoader moduleLoader = _container.Resolve<ModuleLoader>();
// Retrieve the required module information
ModuleInfo module = moduleEnumerator.GetModule(moduleName);
if (module == null)
{
throw new ArgumentException("Requested module was not found",
"moduleName");
}
// Check if the module should be installed
if (installIfNotAvailable)
{
InstallState installationState =
WindowsInstaller.GetFeatureState(_productCode,moduleName);
if (installationState != InstallState.Local &&
installationState != InstallState.Default)
{
// Make sure that the feature is installed locally
WindowsInstaller.ConfigureFeature(
_productCode, moduleName, InstallState.Local);
}
}
// Initialize the module
moduleLoader.Initialize(new ModuleInfo[] { module });
}
First the service looks up the module in the application. This is required to check if the module actually exists at all. You can also use the QueryFeatureState method for this and check for advertised, but I choose to use this method as it more definite in my opinion. The next step is checking if the module is installed, this is only done when the installIfNotAvailable flag is active, otherwise it will just go on and try to initialize the found module.
More information and download
More information can be obtained from the following sources:
- Download: http://www.codeplex.com/CompositeWPFContrib
- Documentation: http://www.codeplex.com/CompositeWPFContrib/Wiki/View.aspx?title=Extended%20Module%20Service
- More information on Windows installer: http://msdn.microsoft.com/en-us/library/cc185688(VS.85).aspx