Dependencies cannot be found using RegistrationServices.RegisterAssembly

30 views Asked by At

I am working on a plugin solution. This solution is one of many plugins we offer that are all installed via the same installer. To this end I have one installer solution for all plugins and every plugin has an implementation of System.Configuration.Install.Installer which is executed through a call to ManagedInstallerClass.InstallHelper .

In one of my plugins the parent software requires me to com register the DLL containing the entry-point to the Plugin. So I execute my regular installation in my Installer Class, find the relevant file in the list of copied files and try to com-register using a call to RegistrationServices.RegisterAssembly(Assembly.LoadFrom(copiedFileLocation), AssemblyRegistrationFlags.SetCodeBase)

This is what my code looks like right now:

Call in my Installer:

List<string> copiedFiles = CopyFiles(Session.InstallPath, installPath);

            var interfaceDllPath = copiedFiles.FirstOrDefault(cf => cf.EndsWith(@"\MyInterface.dll"));
            if (interfaceDllPath != null)
            {
                try
                {
                    var comRegistrar = new ComRegistrar(interfaceDllPath);
                    bool registerSuccess = comRegistrar.Register();
                    if (registerSuccess)
                    {
                        Context.LogMessage($"Successfully COM-registered DLL at {interfaceDllPath}");
                        Context.LogMessage("Enabling AddIn Auto-Load");
                        SetAutoLoadEntry();
                    }
                    else
                    {
                        Context.LogMessage("Failed to com-register DLL (unknown reason).");
                    }
                }
                catch (Exception exception)
                {
                    Context.LogMessage($"Error during com-registration: {exception}");
                    throw;
                }


            }

Actual implementation of ComRegistrar:

public class ComRegistrar
    {
        private readonly string _assemblyPath;

        public ComRegistrar(string assemblyPath)
        {
            if (string.IsNullOrWhiteSpace(assemblyPath))
                throw new ArgumentException("Error in ComRegistrar: assemblyPath cannot be null or empty.");

            _assemblyPath = assemblyPath;
        }
        public bool Register()
        {
            if (String.IsNullOrEmpty(_assemblyPath))
            {
                throw new ArgumentException("Error in ComRegistrar: assemblyPath cannot be null or empty.");
            }
            RegistrationServices registrationService = new RegistrationServices();
            return registrationService.RegisterAssembly(Assembly.LoadFile(_assemblyPath), AssemblyRegistrationFlags.SetCodeBase);
        }

        public bool Unregister()
        {
            if (String.IsNullOrEmpty(_assemblyPath))
            {
                throw new ArgumentException("Error in ComRegistrar: assemblyPath cannot be null or empty.");
            }
            RegistrationServices registrationService = new RegistrationServices();
            registrationService.UnregisterAssembly(Assembly.LoadFile(_assemblyPath));

            //Force GarbageCollect to close file handle on Assembly before uninstall.
            registrationService = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            return true;
        }

    }

Please ignore the manual GC during Unregister (which I had to do because registrationServices would keep an open file handle on the DLL).

During the register method I get the following error: Could not load file or assembly 'AddInAssistant, Version=19.23.516.10987, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

This external dependency is a DLL taken from the parent software, from which we extract some types.

I checked the directory that we're installing to: The AddInAssistant.dll is present there. I checked the versions: AssemblyVersion is the exact number I'm looking for.

Checking the FusLog in the exception :

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  C:\1_Develop_\Repos\External.Interfaces\Interfaces.Installer\Interfaces.Installer\UI.TestShell\bin\Debug\TDM Interfaces Installer.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = AddInAssistant, Version=19.23.516.10987, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\1_Develop_\Repos\External.Interfaces\Interfaces.Installer\Interfaces.Installer\UI.TestShell\bin\Debug\TDM Interfaces Installer.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant.DLL.
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant/AddInAssistant.DLL.
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant.EXE.
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant/AddInAssistant.EXE.

As you can see my Installer project is looking for the DLL only at it's own location, instead of next to the MyInterface.dll

Funny enough, when I check the actual FusLog that I write to Disk it's actually looking at the correct location, somehow it just ignores the fact that the correct DLL is there:

*** Assembly Binder Log Entry  (21.09.2023 @ 14:23:18) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  C:\1_Develop_\Repos\External.Interfaces\Interfaces.Installer\Interfaces.Installer\UI.TestShell\bin\Debug\TDM Interfaces Installer.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = AddInAssistant, Version=19.23.516.10987, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = TDM Interfaces Installer.exe
Calling assembly : TDMInterface, Version=2023.0.0.1, Culture=neutral, PublicKeyToken=null.

LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Using application configuration file: C:\1_Develop_\Repos\External.Interfaces\Interfaces.Installer\Interfaces.Installer\UI.TestShell\bin\Debug\TDM Interfaces Installer.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant.DLL.
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant/AddInAssistant.DLL.
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant.EXE.
LOG: Attempting download of new URL file:///C:/1_Develop_/Repos/External.Interfaces/Interfaces.Installer/Interfaces.Installer/UI.TestShell/bin/Debug/AddInAssistant/AddInAssistant.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/ParentSoftware/AddIns/MyPluginFolder/AddInAssistant.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/ParentSoftware/AddIns/MyPluginFolder/AddInAssistant/AddInAssistant.DLL.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/ParentSoftware/AddIns/MyPluginFolder/AddInAssistant.EXE.
LOG: Attempting download of new URL file:///C:/Program Files (x86)/ParentSoftware/AddIns/MyPluginFolder/AddInAssistant/AddInAssistant.EXE.
LOG: All probing URLs attempted and failed.

Best part:If I do the installation manually, using regasm.exe /codebase no error occurs and I can correctly start the application.

Any advice =/ ?

1

There are 1 answers

0
Rob Mensching On

Don't try to do the registration at install time. Instead, capture the registration during the build and include the registry keys in your installer to avoid complex interactions with the custom action and custom action server.

For the WiX Toolset, we (FireGiant) have an extension that can capture the registration during the build process.