0

I have some generic interface

namespace SimpleLibrary
{
    public interface IImported<T>
    {
        T Value { get; set; }
    }
}

and its implementation in another .dll

namespace ImportedLibrary
{
    [Export(typeof(IImported<>))]
    public class ExportedEntry<T> : IImported<T>
    {
        public T Value { get; set; }
    }
}

Then I make import into another class

namespace SimpleLibrary
{
    public class MainEntry
    {
        [Import]
        public IImported<string> Imported { get; set; }

        public MainEntry()
        {
            try
            {
                var catalog = new DirectoryCatalog(Dir);
                var container = new CompositionContainer(catalog);
                container.ComposeParts(this);

                Imported.Value = "Gomo Sapiens";
            }
            catch (CompositionException ex)
            {
                System.Diagnostics.Debugger.Launch();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debugger.Launch();
            }
        }

        private string Dir
        {
            get
            {
                var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "SimpleLibrary");

                if (!Directory.Exists(dir))
                {
                    dir = AppDomain.CurrentDomain.BaseDirectory;
                }

                return dir;
            }
        }
    }
}

Then I create console application, put .dll with class marked [Export] inside bin\Debug folder and run

static void Main(string[] args)
{
    MainEntry me = new MainEntry();
    Console.WriteLine(me.Imported.Value);

    Console.ReadLine();
}

Everything works fine, console displays line "Gomo Sapiens". However, when I create Wix Installer with some custom action that uses the same MainEntry class and runs after InstallFinalize, import doesn't work:

<Binary Id="ServerActions" SourceFile="path to CA.dll" />

<CustomAction Id="RunMainEntry" BinaryKey="ServerActions" DllEntry="RunMainEntry" Execute="immediate" Return="check" />

<InstallExecuteSequence>
  <Custom Action='RunMainEntry' After='InstallFinalize'>NOT Installed AND NOT WIX_UPGRADE_DETECTED</Custom>
</InstallExecuteSequence>

and custom action code

public class CustomActions
{
    [CustomAction]
    public static ActionResult RunMainEntry(Session session)
    {
        MainEntry me = new MainEntry();
        session.Log(me.Imported.Value);

        return ActionResult.Success;
    }
}

Custom action throws CompositionException. Notice that my installer initially copies all files (.dll and .exe) inside Program Files(x86)\SimpleLibrary, then calls custom action with MEF composition. I want to highlight that at the moment of calling MainEntry constructor all .dlls do lie inside Program Files folder and MEF sees them but can't find what to import. And problem appears only with generics types, simple interfaces do import.

.NET Framework version is 4.5, C# 6.0. CustomAction.config:

<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">          
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Also tried to use MefContrib, situation is the same.

Important thing is that when I run console app manually from Program Files(x86) after installation complete everything again works fine.

So the question is how can I use MEF from Wix's custom actions and if I can't - why?

ivanblin
  • 120
  • 10

1 Answers1

0

I'm fairly certain that when running a custom action the installer will unpack the CA.dll binary into a temporary directory and run it from there. If you added anything to the PATH it will not be reflected within the installer's environment so you won't detect any of the new files.

You can verify with running with msi logging (/l*v log.txt) and/or running with procmon also capturing at the same time.

You will probably need to package the dependent DLLs within the self-extracting CA.dll itself.

You can do this by defining <CustomActionContents> in your custom action project and setting the value of this MSBuild property to a ;-separated list of paths to the dependent DLLs. Then when you build, these DLLs along with your custom action dll will be packaged into the CA.dll which will get extracted to a temporary directory for use at runtime for the custom action.

I actually have another answer with better details about what CustomActionContents is and what it does here. It says "space-delimited" in the comment but I've verified it is ;-delimited. This also gives you the location of the file where the MSBuild target and property is defined if you want to read that code as well.

Brian Sutherland
  • 4,698
  • 14
  • 16
  • I tried your recommendation to include `.dll` with `[Export]` in references of custom actions project, no luck. As you can see in my example I don't try to compose `Main Entry` class from executing assembly location, instead I search for it in `Program Files` folder, where I know exactly `.dll` exists at the moment of execution. So fiasco was expected – ivanblin Dec 14 '17 at 07:36
  • Hmm it should work since you can manually put the files in place to get it working. I would do a run capturing file data with procmon and see what it is trying to load and where it is looking. Maybe that can shed some light on this. – Brian Sutherland Dec 14 '17 at 15:27
  • I'm not sure I understand exactly how procmon works but if it just gives an ability to watch where installer is trying to look for `.dll`, I can do it in debug mode – ivanblin Dec 15 '17 at 07:58