Custom action calling into .NET 4 assembly

Sep 11, 2015 at 7:27 AM
Edited Sep 11, 2015 at 7:29 AM
I have a .NET library (dll) of helper methods specific to installing my product, that targets .NET 4.

However, the "main" WIX project is targeting .NET 3.5 (as specified in the documentation) (meaning it runs on the 2.0 runtime). It has custom actions that call into the above 4.0 dll.

When running the generated MSI, i get "BadImageFormat" exceptions, when the WIX runtime (running on 2.0 runtime) tries to load my 4.0 dll.

If I had direct access to a .NET 3.5 .exe, I could convince it to load a 4.0 dll by using the <supportedVersion 4.0 [...]> trick in its app.config.

However, the WIX runtime is loaded (by msiexec), as far as I can tell from the stack trace, through Interop.

Does anybody have a suggestion on how I could get this to run?

PS. I also tried making the "main" Wix project target 4.0 directly instead of 3.5, but then my setup dialogs will just fail to show.

Thanks
Coordinator
Sep 11, 2015 at 12:21 PM
Edited Sep 11, 2015 at 12:28 PM
At install time there is actually no ".NET 3.5 .exe" as the managed actions assembly is packed into a native DLL hosted by MSI runtime, which makes the decision on what version of CLR to load. The only control that you can have (that I am aware of) is via the *.config file that is embedded along with the assembly by the MakeSfxCA.exe tool. By default Wix# compiler generates one for you if you didn't specified it but if you want to have a more precise control you may want to supply the custom config file. You will need to set Project.CAConfigFile property for that. You can read about this here.

I do remember that it was something really tricky about the dialogs and CLR v4.0 so as a plan B you may consider possibility of moving your whole custom action into a separate (v4.0) assembly so it doesn't collide with your (v3.5) dialogs. My speculation here is that MSI may be hosting multiple CLR versions on the target system at the same time. While technically it is possible it is a question if the WiX team actually implemented it this way.

Controlling the MSI's CLR version is tricky so when you find the solution please consider sharing it so I can reflect it in the wiki documentation and others can benefit from it.

Cheers,
Oleg
Coordinator
Sep 12, 2015 at 9:21 AM
Edited Sep 12, 2015 at 9:26 AM
I have done some experimenting and can confirm now that you definitely can target .NET 4.0 in your custom actions. This is a good news, though there will be a bad one as well.

The default *.config file consumed by MakeSfxCA.exe tool is capable of remapping the assembly hosting at runtime for .NET 4.0. Thus if you compile the following code for .NET 4.0 it will work just fine:
class Script
{
    static public void Main()
    {
        var project = new Project("CustomActionTest",
                          new ManagedAction("MyAction", 
                                            Return.check, 
                                            When.After, 
                                            Step.InstallInitialize, 
                                            Condition.NOT_Installed));
        project.BuildMsi();
    }
}

public class CustomActions
{
    [CustomAction]
    public static ActionResult MyAction(Session session)
    {
        MessageBox.Show("Hello World! (CLR: v"+Environment.Version+")", 
                        "Embedded Managed CA "+ ((IntPtr.Size == 8) ? "(x64)" : "(x86)"));
        return ActionResult.Success;
    }
}
Runtime result:
Image

The same works OK for ManagedProject events as well.

However there is a bad news. As you correctly mentioned custom (managed) UI cannot be hosted if it is compiled on .NET 4.0. This limitation has nothing to do with Wix# as it is caused by the MSI/WiX implementation of EmbeddedUI model, which is used by Wix# for ManagedUI.

If you want to try to find a work around the best way to approach it is to work with the "Wix# Samples\Custom_UI\EmbeddedUI" sample. It is in fact the sample from WiX code base. One would expect that by playing with the project TargerFramework and *.config it would be possible to target .NET 4.0 but sadly it's not the case. Somehow CLR initialization of the managed EmbeddeUI does not follow the same algorithm as for managed CustomAcions. If you can find the way to fool MSI/WiX runtime please share your findings with me and I will incorporate your findings into Wix#.

In this situation you have no other choice but to move your Custom Action into a separate assembly compiled for .NET 4.0. This way you will preserve your UI in it's v.3.5 compiled form but your custom actions will be compiled as you need. The 'Wix# Samples\DTF (ManagedCA)\Different Scenarios\ExternalAssembly' will help you to integrate the external assembly in your codebase.

Good luck!
May 19, 2016 at 3:51 AM
I've confirmed that the sample at

src\WixSharp.Samples\Wix# Samples\DTF (ManagedCA)\Different Scenarios\ExternalAssembly

builds and runs as expected, but the sample doesn't really test building either CustomAction.dll or DisplayMessage.dll for > 3.5 .NET CLR.

And when I add this to my ManagedUI installer after building SetupCustomAction.dll for .NET 4.0:
            var project = new ManagedProject("MyProject"
                , new ManagedAction("CustomAction") {
                    ActionAssembly = @"..\SetupCustomAction\bin\Debug\SetupCustomAction.dll",
                    RefAssemblies = new string[0],
                }
I get the following error in the log:
Info: Calling custom action SetupCustomAction!CustomAction.CustomActions.CustomAction
Info: Error: could not load custom action class CustomAction.CustomActions from assembly: SetupCustomAction
Info: System.BadImageFormatException: Could not load file or assembly 'SetupCustomAction' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.
It works fine if I build SetupCustomAction for .NET 3.5

How can I build and specify a CustomAction assembly that allows .NET 4.5+??
d
Coordinator
May 19, 2016 at 7:04 AM
The error message indicates that the target system doesn't have .NET 4.5+ available. You cannot build a custom action that requires .NET4.5+ and run it on the system where this runtime is unavailable. You have here a typical 'chicken-egg' problem, which is quite common fro deployment solutions. For example you cannot run MSI setup on the PC that doesn't have MSI services installed.

The most common way of solving this problem is bootstrapping. Thus you can bundle your product with the required dependency package and schedule it to be deployed before the main product. This is what Burn (WiX bootstrapper does). The samples are in the Wix# distro. And WiX bootstrapper is so clever that it can even bootstrapp the required .NET version before displaying its own GUI. That very GUI that may be implemented as a managed app (Custom BA).

However sometimes it's just enough to build your setup with the minimal dependencies. For example building against lowest possible version of .NET (the default version of .NET that comes with your OS). Of course it's not so flexible as bootstrapper but sometimes it's just more practical. But if it's not an option for you then bootstrapper is the way to go.
May 19, 2016 at 11:44 AM
I was running the test on my development system which has 4.5 through 4.6.1 on it.

Is it possible the error message means something else?
Perhaps a config file configuration issue?
Coordinator
May 20, 2016 at 12:17 AM
> Is it possible the error message means something else?
Actually it is. The very same error will be generated if you are trying to load x64 assmebly under x86 CLR or vise versa. This can happen if you didn't build your managed assembly with "AnyCPU" target.

Also as the message indicates it can be a dependency problem. But this one can be easily ruled out by testing it with any of the unmodified ManagedCustolmActions samples.