Prism is a well-know framework to develop applications that support modularity, navigation, communication between loosely coupled components and more !

Each module is loaded automatically (OnDemand property of Module attribute set to false) or on demand (OnDemand set to true) but in all case, you can’t define in which order the modules are loaded. By default, they are loaded by using their name and you don’t have any way to change that.

In my case, I wanted to be able to load my modules in specific order. So, after taking a look on Internet, I’ve found this post on StackOverFlow: http://stackoverflow.com/questions/1296642/how-to-control-the-order-of-module-initialization-in-prism

The code work fine except for modules that are asked to be loaded on demand. So I needed to find a way to be able to specify the loading order of each module, even if they have to be started on demand) and, for that, I’ve been inspired by the previous code.

Indeed, I first create the Priority attribute:

/// <summary>
/// Allows the order of module loading to be controlled.  Where dependencies
/// allow, module loading order will be controlled by relative values of priority
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class PriorityAttribute : Attribute
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="priority">the priority to assign</param>
    public PriorityAttribute(int priority)
    {
        this.Priority = priority;
    }

    /// <summary>
    /// Gets or sets the priority of the module.
    /// </summary>
    /// <value>The priority of the module.</value>
    public int Priority { get; private set; }
}

Then, I created a dedicated ModuleCatalog, inspired by the PrioritizedDirectoryCatalog (from the post of StackOverFlow) and the DirectoryModuleCatalog (from the Prism’s sources):

public class PrioritizedDirectoryModuleCatalog : DirectoryModuleCatalog
{
    protected override void InnerLoad()
    {
        if (string.IsNullOrEmpty(this.ModulePath))
            throw new InvalidOperationException("The ModulePath cannot contain a null value or be empty.");

        if (!Directory.Exists(this.ModulePath))
            throw new InvalidOperationException(
                string.Format(CultureInfo.CurrentCulture, "Directory {0} was not found.", this.ModulePath));

        AppDomain childDomain = this.BuildChildDomain(AppDomain.CurrentDomain);

        try
        {
            List<string> loadedAssemblies = new List<string>();

            var assemblies = (
                                 from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
                                 where !(assembly is System.Reflection.Emit.AssemblyBuilder)
                                    && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
                                    && !String.IsNullOrEmpty(assembly.Location)
                                 select assembly.Location
                             );

            loadedAssemblies.AddRange(assemblies);

            Type loaderType = typeof(ModulePriorityLoader);

            if (loaderType.Assembly != null)
            {
                var loader =
                    (ModulePriorityLoader)
                    childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
                loader.LoadAssemblies(loadedAssemblies);
                this.Items.AddRange(this.Sort(loader.GetModuleInfos(this.ModulePath)));
            }
        }
        finally
        {
            AppDomain.Unload(childDomain);
        }
    }

    /// <summary>
    /// Sort modules according to dependencies and Priority
    /// </summary>
    /// <param name="modules">modules to sort</param>
    /// <returns>sorted modules</returns>
    protected override IEnumerable<ModuleInfo> Sort(IEnumerable<ModuleInfo> modules)
    {
        Dictionary<string, int> priorities = GetPriorities(modules);

        //call the base sort since it resolves dependencies, then re-sort 
        var result = new List<ModuleInfo>(base.Sort(modules));
        result.Sort((x, y) =>
        {
            string xModuleName = x.ModuleName;
            string yModuleName = y.ModuleName;

            //if one depends on other then non-dependent must come first
            //otherwise base on priority
            if (x.DependsOn.Contains(yModuleName))
                return 1; //x after y
            else if (y.DependsOn.Contains(xModuleName))
                return -1; //y after x
            else
                return priorities[xModuleName].CompareTo(priorities[yModuleName]);
        });

        return result;
    }

    /// <summary>
    /// Get the priorities
    /// </summary>
    /// <param name="modules"></param>
    /// <returns></returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
    public Dictionary<string, int> GetPriorities(IEnumerable<ModuleInfo> modules)
    {
        //retrieve the priorities of each module, so that we can use them to override the 
        //sorting - but only so far as we don't mess up the dependencies
        var priorities = new Dictionary<string, int>();
        var assemblies = new Dictionary<string, Assembly>();

        foreach (ModuleInfo module in modules)
        {
            if (!assemblies.ContainsKey(module.Ref))
            {
                //LoadFrom should generally be avoided appently due to unexpected side effects,
                //but since we are doing all this in a separate AppDomain which is discarded
                //this needn't worry us
                assemblies.Add(module.Ref, Assembly.LoadFrom(module.Ref));
            }

            Type type = assemblies[module.Ref].GetExportedTypes()
                .Where(t => t.AssemblyQualifiedName.Equals(module.ModuleType, StringComparison.Ordinal))
                .First();

            var priorityAttribute =
                CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
                    cad => cad.Constructor.DeclaringType.FullName == typeof(PriorityAttribute).FullName);

            int priority;
            if (priorityAttribute != null)
            {
                priority = (int)priorityAttribute.ConstructorArguments[0].Value;
            }
            else
            {
                priority = 0;
            }

            priorities.Add(module.ModuleName, priority);
        }

        return priorities;
    }

    /// <summary>
    /// Local class to load assemblies into different appdomain which is then discarded
    /// </summary>
    private class ModulePriorityLoader : MarshalByRefObject
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        internal void LoadAssemblies(IEnumerable<string> assemblies)
        {
            foreach (string assemblyPath in assemblies)
            {
                try
                {
                    Assembly.ReflectionOnlyLoadFrom(assemblyPath);
                }
                catch (FileNotFoundException)
                {
                    // Continue loading assemblies even if an assembly can not be loaded in the new AppDomain
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        internal ModuleInfo[] GetModuleInfos(string path)
        {
            DirectoryInfo directory = new DirectoryInfo(path);

            ResolveEventHandler resolveEventHandler =
                delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };

            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;

            Assembly moduleReflectionOnlyAssembly =
                AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(
                    asm => asm.FullName == typeof(IModule).Assembly.FullName);

            Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);

            IEnumerable<ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(directory, IModuleType);

            var array = modules.ToArray();
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;

            return array;
        }

        private static IEnumerable<ModuleInfo> GetNotAllreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType)
        {
            List<FileInfo> validAssemblies = new List<FileInfo>();
            Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();

            var fileInfos = directory.GetFiles("*.dll")
                .Where(file => alreadyLoadedAssemblies
                                   .FirstOrDefault(
                                   assembly =>
                                   String.Compare(Path.GetFileName(assembly.Location), file.Name,
                                                  StringComparison.OrdinalIgnoreCase) == 0) == null);

            foreach (FileInfo fileInfo in fileInfos)
            {
                Assembly assembly = null;
                try
                {
                    assembly = Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName);
                    validAssemblies.Add(fileInfo);
                }
                catch (BadImageFormatException)
                {
                    // skip non-.NET Dlls
                }
            }

            return validAssemblies.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName)
                                        .GetExportedTypes()
                                        .Where(IModuleType.IsAssignableFrom)
                                        .Where(t => t != IModuleType)
                                        .Where(t => !t.IsAbstract)
                                        .Select(type => CreateModuleInfo(type)));
        }

        private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
        {
            Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(
                asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
            if (loadedAssembly != null)
            {
                return loadedAssembly;
            }

            AssemblyName assemblyName = new AssemblyName(args.Name);
            string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
            if (File.Exists(dependentAssemblyFilename))
            {
                return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
            }
            return Assembly.ReflectionOnlyLoad(args.Name);
        }

        private static ModuleInfo CreateModuleInfo(Type type)
        {
            string moduleName = type.Name;
            List<string> dependsOn = new List<string>();
            bool onDemand = false;
            var moduleAttribute =
                CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
                    cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);

            if (moduleAttribute != null)
            {
                foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
                {
                    string argumentName = argument.MemberInfo.Name;
                    switch (argumentName)
                    {
                        case "ModuleName":
                            moduleName = (string)argument.TypedValue.Value;
                            break;

                        case "OnDemand":
                            onDemand = (bool)argument.TypedValue.Value;
                            break;

                        case "StartupLoaded":
                            onDemand = !((bool)argument.TypedValue.Value);
                            break;
                    }
                }
            }

            var moduleDependencyAttributes =
                CustomAttributeData.GetCustomAttributes(type).Where(
                    cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);

            foreach (CustomAttributeData cad in moduleDependencyAttributes)
            {
                dependsOn.Add((string)cad.ConstructorArguments[0].Value);
            }

            ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
            {
                InitializationMode =
                    onDemand
                        ? InitializationMode.OnDemand
                        : InitializationMode.WhenAvailable,
                Ref = type.Assembly.CodeBase,
            };
            moduleInfo.DependsOn.AddRange(dependsOn);

            return moduleInfo;
        }
    }
}

Now, in your Bootstrapper, you simply have to use your new ModuleCatalog:

protected override IModuleCatalog CreateModuleCatalog()
{
    return new PrioritizedDirectoryModuleCatalog { ModulePath = ".\Modules" };
}

Then, decorate each of your module with this new attribute:

[Module(ModuleName = "RootModule", OnDemand = false)]
[Priority(-1)]
public class RootModule : IModule

[Module(ModuleName = "ModuleA", OnDemand = true)]
[Priority(1)]
public class ModuleAModule : IModule

All that’s all ! If you enumerate all the modules, you will be able to see that they are ordered as you define it: RootModule will be the first, then, ModuleA (even if the sort on the name will produce a different result):

[Dependency]
public IModuleCatalog ModuleCatalog { get; set; }

[Dependency]
public IModuleManager ModuleManager { get; set; }

foreach (var module in this.ModuleCatalog.Modules.Where(m => m.InitializationMode != InitializationMode.WhenAvailable))
{
    this.ModuleManager.LoadModule(module.ModuleName);
}

 

Happy coding!

AvalonEdit, a core component of SharpDevelop 4.0,  is a very useful component for anyone who want to re-create, in its application, a code/text editor. It provides some great features like:

  1. Autocompletion
  2. Tooltip
  3. Text highlighting
  4. An much more !

Sample Image

But if you need/want to create your own code editor, there are some steps that you’ll need to do and that be a bit hard to perform. I think, for example, to the following case:

  1. Find the word under the mouse to display it’s documentation on the tooltip
  2. Find the word before a dot (“.”) to display the correct values in the autocompletion box
  3. Add a custom highlighting

To help you in these tasks, I’ve create some extension methods that you can use directly:

public static class AvalonEditExtensions
{
    public static IHighlightingDefinition AddCustomHighlighting(this TextEditor textEditor, Stream xshdStream)
    {
        if (xshdStream == null)
            throw new InvalidOperationException("Could not find embedded resource");

        IHighlightingDefinition customHighlighting;

        // Load our custom highlighting definition
        using (XmlReader reader = new XmlTextReader(xshdStream))
        {
            customHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
        }

        // And register it in the HighlightingManager
        HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", null, customHighlighting);

        return customHighlighting;
    }

    public static IHighlightingDefinition AddCustomHighlighting(this TextEditor textEditor, Stream xshdStream, string[] extensions)
    {
        if (xshdStream == null)
             throw new InvalidOperationException("Could not find embedded resource");

        IHighlightingDefinition customHighlighting;

        // Load our custom highlighting definition
        using (XmlReader reader = new XmlTextReader(xshdStream))
        {
            customHighlighting = HighlightingLoader.Load(reader, HighlightingManager.Instance);
        }

        // And register it in the HighlightingManager
        HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", extensions, customHighlighting);

        return customHighlighting;
    }

    public static string GetWordUnderMouse(this TextDocument document, TextViewPosition position)
    {
        string wordHovered = string.Empty;

        var line = position.Line;
        var column = position.Column;

        var offset = document.GetOffset(line, column);
        if (offset >= document.TextLength)
            offset--;

        var textAtOffset = document.GetText(offset, 1);

        // Get text backward of the mouse position, until the first space
        while (!string.IsNullOrWhiteSpace(textAtOffset))
        {
            wordHovered = textAtOffset + wordHovered;

            offset--;

            if (offset < 0)
                break;

            textAtOffset = document.GetText(offset, 1);
        }

        // Get text forward the mouse position, until the first space
        offset = document.GetOffset(line, column);
        if (offset < document.TextLength - 1)
        {
            offset++;

            textAtOffset = document.GetText(offset, 1);

            while (!string.IsNullOrWhiteSpace(textAtOffset))
            {
                wordHovered = wordHovered + textAtOffset;

                offset++;

                if (offset >= document.TextLength)
                    break;

                textAtOffset = document.GetText(offset, 1);
            }
        }

        return wordHovered;
    }

    public static string GetWordBeforeDot(this TextEditor textEditor)
    {
        var wordBeforeDot = string.Empty;

        var caretPosition = textEditor.CaretOffset - 2;

        var lineOffset = textEditor.Document.GetOffset(textEditor.Document.GetLocation(caretPosition));

        string text = textEditor.Document.GetText(lineOffset, 1);

        // Get text backward of the mouse position, until the first space
        while (!string.IsNullOrWhiteSpace(text) && text.CompareTo(".") > 0)
        {
            wordBeforeDot = text + wordBeforeDot;

            if (caretPosition == 0)
                break;

            lineOffset = textEditor.Document.GetOffset(textEditor.Document.GetLocation(--caretPosition));

            text = textEditor.Document.GetText(lineOffset, 1);
        }

        return wordBeforeDot;
    }
}

To use these methods, it’s really simple:

private void LoadCustomHighlighting()
{
    using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("CustomHighlighting.xshd"))
    {
        textEditor.SyntaxHighlighting = textEditor.AddCustomHighlighting(s);
    }
}

private void textEditor_MouseHover(object sender, MouseEventArgs e)
{
    var pos = textEditor.GetPositionFromPoint(e.GetPosition(textEditor));
    if (pos != null)
    {
        string wordHovered = textEditor.Document.GetWordUnderMouse(pos.Value);

        e.Handled = true;
    }
}

void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
    if (e.Text == ".")
    {
        var previousWord = textEditor.GetWordBeforeDot();
    }
}

As you can see, the whole difficult part is already managed by the extension methods. Of course, the code can be a bit ugly but, at least, it’s working fine. But feel free to post a comment if you have ideas to improve it !

 

Thanks and happy coding!

Wix is a technology that aims to help developer create Windows Installer file for the projects they’re developing. This is a very complete product but it can be a bit difficult to understand for new users. I’ll try to explain you how to create your first basic installer. I hope to be able to write more about Wix as soon as possible!

First, you need to download and install the latest version of Wix (latest stable version is 3.5, and the beta one is 3.6). Once it’s installed, just start Visual Studio and create a new project:

image

Wix projects are simple XML files (with the wxs extension) which describe the list of files that need to be copied on the target machine. They also define the various actions that can be performed during the installation (registry keys to be created, components that need to be installed, etc.).

A basic Wix file contains, at least, the following XML elements:

  1. Product: it’s used to describe the Windows Installer to be create (name, language, manufacturer, etc.)
  2. Package: this element contains the information that goes in the MSI’s summary information stream
  3. Directory: this is the element that will define in which folder the files need to be deployed.
  4. Component: this represents the component that needs to be deployed/installed. It can be a file, a registry key, a resource, etc.
  5. Feature: Each product is defined using features, which contains the components that have been declared above.

So, let’s take a look at this simple (but working) example:

<?xml version='1.0'?>

<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>

   <Product Id="5168aa41-582e-4cb2-948f-cf2a8e5dff28" Name="My Addin Package" Language="1033" 

            Version="1.0.0.0" Manufacturer="Thomas Lebrun" UpgradeCode="e22d0f13-3cfa-4644-a466-807a93103c23" >

      <Package Description='My first Windows Installer package'

               Manufacturer="Thomas Lebrun" InstallerVersion='200' Compressed='yes' />

 

      <Directory Id="TARGETDIR" Name="SourceDir">

         <Component Id="MyComponent" Guid="D9247E78-1CB0-4F71-8D28-9F72581A647B" />

      </Directory>

      <Feature Id="MyFeature" Title="My 1st Feature" Level="1">

         <ComponentRef Id="MyComponent" />

      </Feature>

   </Product>

</Wix>

Then, we need to  define which file we want to add to our installer. Indeed, for now, we have specified that a component will be deployed in the folder specify by user but we haven’t set which files will be in that folder. For that, we have to modify the component element to include a File element, which will define:

  1. Its Id
  2. Its name on the disk
  3. The source of the file to get and include in the installer

Again, here is a working example:

<Directory Id="TARGETDIR" Name="SourceDir">

    <Component Id="MyComponent" Guid="D9247E78-1CB0-4F71-8D28-9F72581A647B">

        <File Id="myDll" Name="Sample.dll" Source="Sample.dll" />

    </Component>

</Directory>

Now, if you compile the project, you can go in the bin\Debug (or bin\Release) folder to see your MSI file! Visual Studio handles the call to the executable files (candle and light) that need to be run to create the file!

If you want to create some entries in the registry, it can also be done simply:

<Component Id="MyRegistryKeys" Guid="4D1643FD-A530-4AA0-9D07-BF0469C3D505">

    <RegistryKey Root="HKCU" Key="Software\Microsoft\Office\Excel\Addins\My Addin" Action="createAndRemoveOnUninstall">

        <RegistryValue Type="string" Name="Description" Value="My first Excel addin." />

        <RegistryValue Type="string" Name="FriendlyName" Value="My Addin" />

        <RegistryValue Type="integer" Name="LoadBehavior" Value="3" />

        <RegistryValue Type="string" Name="Manifest" Value="[INSTALLLOCATION]MyAddin.vsto|vstolocal" />

    </RegistryKey>

</Component>

It’s interesting to note that the RegistryKey element has the attribute Action and its value is createAndRemoveOnUninstall, that allows the installer to remove, during the uninstall process, the registry key that has been create.

But, most of time, you need to perform more complex actions when your application is being installed. As an example, it might be necessary for your installer to check if a registry key exists. To do that, you can use the RegistrySearch action which, as you can guess, can be used to perform a search in the registry to locate a particular key.

<Property Id="VSTORUNTIMEREDIST">

    <RegistrySearch Id="VSTORuntimeRedist" Root="HKLM" Key="SOFTWARE\Microsoft\VSTO Runtime Setup\v4R" Name="Version" Type="raw" />

</Property>

 

<Property Id="OFFICERUNTIME">

    <RegistrySearch Id="OfficeRuntime" Root="HKLM" Key="SOFTWARE\Microsoft\VSTO Runtime Setup\v4" Name="Version" Type="raw" />

</Property>

Here, we use the same action twice to check in the registry if the VSTO 2010 Runtime has been installed (as this one can be installed as part of Office 2010 or as part of Visual Studio 2010, we need to perform 2 checks). Each result of the search is stored in a property and then, it’s possible to use these properties in Conditions element that allow you to display an error message if a specific condition is not satisfied:

<!-- Check if if VSTO 2010 Runtime has been installed by VSTO -->

<Condition Message="The Visual Studio 2010 Tools for Office Runtime is not installed. Please run setup.exe.">

    <![CDATA[Installed OR VSTORUNTIMEREDIST>="10.0.30319"]]>

</Condition>

 

<!-- Check if if VSTO 2010 Runtime has been installed by Office 2010 -->

<Condition Message="The Visual Studio 2010 Tools for Office Runtime is not installed. Please run setup.exe.">

    <![CDATA[Installed OR VSTORUNTIMEREDIST>="10.0.30319" OR OFFICERUNTIME>="10.0.21022"]]>

</Condition>

If you want your installer to look for a specific component, during installation, you can use the ComponentSearch action that will look for the specified product code (identified by a Guid) and let you know if the component is installed or not:

<Property Id="SHAREDPIA">

    <ComponentSearch Id="SharedPia" Guid="64E2917E-AA13-4CA4-BFFE-EA6EDA3AFCB4" />

</Property>

 

<Property Id="EXCELPIA">

    <ComponentSearch Id="ExcelPia" Guid="EA7564AC-C67D-4868-BE5C-26E4FC2223FF" />

</Property>

This time again, we need to use these properties (that allows us to check if the Office Shared PIA and Excel PIA are installed) in a Condition element:

<!-- Search for Office 2010 Shared PIA -->

<Condition Message="A required component for interacting with Excel 2010 is not available. Please run setup.exe.">

    <![CDATA[Installed OR SHAREDPIA]]>

</Condition>

 

<!-- Search for Office 2010 Excel PIA -->

<Condition Message="A required component for interacting with Excel 2010 is not available. Please run setup.exe.">

    <![CDATA[Installed OR EXCELPIA]]>

</Condition>

Now; just compile your project and you’ll get a MSI file that, if you run it, will perform some checks in the registry/components before installing your product. But there is a drawing back using this method. If a prerequisite is missing, you’ll have a MessageBox that will appear and display the message you’ve chosen to show. But users have no way to install the missing components. So we need to find a way to include, in our setup file, the installers of those components.

For that, you can edit the project file of your Wix project, in Visual Studio. Simply right click on your project and choose “Edit Project File”. This will show you the content of the file that is a simple MSBuild file. So, first, inside the Project element, add an ItemGroup element that will define the list of all the prerequisites components that will need to be include in the setup:

<ItemGroup>

    <BootstrapperFile Include="Microsoft.Net.Framework.3.5.SP1">

      <ProductName>.NET Framework 3.5</ProductName>

    </BootstrapperFile>

    <BootstrapperFile Include=".NETFramework,Version=v4.0">

      <ProductName>.NET Framework 4.0</ProductName>

    </BootstrapperFile>

    <BootstrapperFile Include="Microsoft.Windows.Installer.3.1">

      <ProductName>Windows Installer 3.1</ProductName>

    </BootstrapperFile>

    <BootstrapperFile Include="Microsoft.VSTORuntime.4.0">

      <ProductName>VSTO 2010 Runtime</ProductName>

    </BootstrapperFile>

    <BootstrapperFile Include="Microsoft.Office.PIARedist.2007">

      <ProductName>Office 2007 PIA</ProductName>

    </BootstrapperFile>

    <BootstrapperFile Include="Microsoft.Office.PIARedist.2010">

      <ProductName>Office 2010 PIA</ProductName>

    </BootstrapperFile>

</ItemGroup>

Now, we need to tell Wix that these components need to be deployed with our installer. So, at the end of the file, uncomment the target named “After Build” and add the following code:

<GenerateBootstrapper ApplicationFile="$(TargetFileName)"

                      ApplicationName="Intuitive Query Excel Addin"

                      BootstrapperItems="@(BootstrapperFile)" 

                      ComponentsLocation="Relative" 

                      CopyComponents="True" 

                      OutputPath="$(OutputPath)" 

                      Path="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\" />

This code is used to generate a bootstrapper, a setup.exe file that will be used to check the conditions and, if they’re not respected, will launch the setup of the appropriate missing component. The most important parts here are:

  1. BootstrapperItems: that matches to the ItemGroup element containing all the BootstrapperFile. Each element in this group will be defined as a requirement needed to be deployed with your application.
  2. Path: this points to the local path of the prerequisites that should be installed with your installer.

As an example, look at this picture that show all the available package that are on my computer:

SNAGHTML10a2feee

Each of these folders contains components that can be part of the installation of your application (but which need to be installed BEFORE your application).

If you compile your project, you’ll see that a lot of files/folders are generated:

image

The folders contains the prerequisites that are mandatory to install your application. The MSI file is your installer, the one you’ve build with your Wix project. And the setup.exe file is a simple bootstrapper, in charge of detecting the prerequisites, installing them if they are not present and launching the MSI once the system is ready !

Your Wix project is now finished but you can go further and decide to use it to build your application during continuous integration. If you are using Team Foundation Server to perform your builds, this is very easy to do because the Wix project you’ve created in Visual Studio, with the wixproj extension, is an MSBuild file. So, during your build, you can just run the MSBuild.exe application, giving it your Wix project:

image

As you’ve seen during this article, Wix files are only XML files so it might not be much fun to modify them in Visual Studio, even if we do get Intellisense. So you can take a look at the tool named WixEdit, that allow you to edit Wix files (with extension wxs) through a graphical interface:

SNAGHTML1464332b

Finally, here is a little tip if you already have a MSI file and want to get the Wix file corresponding to it. You can use the command-line tool Dark.exe’ (located in C:\Program Files\Windows Installer XML v3.6\bin or C:\Program Files (x86)\Windows Installer XML v3.6\bin) to decompile the MSI file.
Be careful, the file might be a quite big and hard to understand but you should be able to get a lot of useful information from it.

 

I hope this article helped you to better understand Wix and how to use it to build your own installers. I’ll try to publish more articles on Wix as soon as possible.

 

Happy coding!

With one of my colleague (and friend), Jonathan ANTOINE, we have just release a new book, in French, on the MVVM Pattern. It targets WPF, Silverlight and Windows Phone technologies.

clip_image001[6]

Its title is “MVVM, De la découverte à la maitrise” and aims to help developers to understand all the principles of the pattern. It is available as an eBook from our publisher: http://www.digitbooks.fr/catalogue/mvvm-antoine-lebrun.html but you can also order it as a paper book !

We hope you’ll appreciate it! Oh and, by the way, don’t hesitate to share the information if you like it!

Thanks and happy coding !