Encrypt configuration file in a custom managed action on x64

When I install a .NET (or ASP .NET) application, I like to encrypt the sensitive parts of the configuration file like the connection strings. So I have been used to create a Custom Installer class to achieve this. There are a few tricky things to take into account, like:

  • when your configuration is loaded by the installer, it should be able to resolve embedded dependencies (like Enterprise Library assemblies) even when they are not in the GAC.
  • this class accepts a EXEPATH parameter that contains the path of the application which application file is to be encrypted. It should then be terminated by an extra \ character.

For the record, here is how I do this:

[RunInstaller(true)]
public partial class EncryptConfigInstaller:
    Installer
{

    public EncryptConfigInstaller()
    {
        InitializeComponent();
    }

    public override void Install(IDictionary stateSaver)
    {
        base.Install(stateSaver);

        string exePath=Context.Parameters["exepath"].TrimEnd('\\');

        AppDomain.CurrentDomain.SetData("EXEDIR", Path.GetDirectoryName(exePath));
        AppDomain.CurrentDomain.AssemblyResolve+=_AssemblyResolver;
        try
        {
            EncryptConfiguration(ConfigurationManager.OpenExeConfiguration(exePath));
        } finally
        {
            AppDomain.CurrentDomain.AssemblyResolve-=_AssemblyResolver;
        }
    }

    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        AssemblyName name=new AssemblyName(args.Name);

        return Assembly.LoadFile(Path.Combine((string)AppDomain.CurrentDomain.GetData("EXEDIR"), string.Concat(name.Name, ".dll")));
    }

    private static void EncryptConfiguration(Configuration config)
    {
        EncryptSection(config.GetSection("connectionStrings"));

        config.Save(ConfigurationSaveMode.Modified);
    }

    private static void EncryptSection(ConfigurationSection section)
    {
        if ((section!=null) && (!section.SectionInformation.IsProtected))
        {
            section.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
            section.SectionInformation.ForceSave=true;
        }
    }

    private static ResolveEventHandler _AssemblyResolver=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

Then even if I know that managed actions are considered evil, as I do not think I have a choice here, I am using the well known WiX Custom Managed Actions trick. So everything is fine in a perfect world.

Except when a client tried to install one of my applications on a x64 platform (namely Windows Server 2008). Once solved the obvious native libraries problem, I was still puzzled by the fact that the user who had installed the application was the only one able to run it! It was only after a couple of days of wrong conjectures, poor tricks and unproductive Google searches that I cornered the problem: the encryption process was broken.

Extensive reasons behind that can be found here, but basically it is because InstallUtilLib.dll, which is used to launch Installer classes, is a native library. As such, you should use the 64 bits version on a x64 platform. So I ended up copying both versions in an independent lib folder, and here is the magic corresponding WiX line:

<Binary Id="InstallUtilBinary" SourceFile="$(sys.CURRENTDIR)..\..\lib\$(sys.BUILDARCH)\InstallUtilLib.dll" />

Yes, I learned a lot about x64 lately. But I guess I am not done yet :-)