If you use the XmlConfigurator.Configure() . log4net function in a setup custom action to initialize the log4net logging, you can get the error:

Could not load file or assembly ‘log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821′ or one of its dependencies. The system cannot find the file specified.

This is because the msi package installs the assembly containing the custom action that uses the log4net assembly in a folder: C:\Program Files\MyCompany\MyProduct, but when the setup is started from a folder C:\Temp, the current working folder is C:\Windows\SysWOW64 on a x64 windows. The folder C:\Program Files\MyCompany\MyProduct is there for not included in the standard .NET assembly search path. To add the folder C:\Program Files\MyCompany\MyProduct to the .NET assemlby search path use the following class:

By the way this will also occur when you use custom configuration sections in you’re the App.config of you’re program (exe) and want to use the custom configuration sections during you’re msi installation with a custom setup action.

public class AssemblyLoader
    {
        private string _productInstallationFolder = null;
        /// <summary>
        /// Returns C:\Program Files\MyCompany\MyProduct or C:\Program Files (x86)\MyCompany\MyProduct depending on the platform.
        /// </summary>
        public string ProductInstallationFolder
        {
            get
            {
                if (string.IsNullOrEmpty(_productInstallationFolder))
                {
                    var programFilesFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFiles);
                    _productInstallationFolder = Path.Combine(programFilesFolder, @"MyCompany\MyProduct");
                }
                return _productInstallationFolder;
            }
            set
            {
                _productInstallationFolder = value;
            }
        }
        /// <summary>
        /// Add an evenhandler for adding a folder to the .NET assemlby search path.
        /// </summary>
        public void BindAssemblyResolveEventHandler()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.AssemblyResolve += this.LoadAssemlbyFromProductInstallationFolder;
        }
        /// <summary>
        /// This function is called when the .NET runtime searches for an assemlby to load and can't find that assembly in the current search path.
        /// The current search path includes "bin folder application", the global assemlby cache, system32 folder etc.
        ///
        /// This function adds a folder to the current search path at runtime.
        ///
        /// An assembly can be a dll or exe, the ResolveEventArgs argument does not cotain this information.
        /// The code will first check if a dll exist in the given folder, if found it loads the dll.
        /// If the dll is not found, the code checks if an executable exists in the given folder, if found it loads the exe.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public Assembly LoadAssemlbyFromProductInstallationFolder(object sender, ResolveEventArgs args)
        {
            Assembly result = null;
            if (args != null && !string.IsNullOrEmpty(args.Name))
            {
                var folderPath = (new FileInfo(this.ProductInstallationFolder)).DirectoryName;
                var assemblyName = args.Name.Split(new string[] { "," }, StringSplitOptions.None)[0];
                var assemblyExtension = "dll";
                var assemblyPath = Path.Combine(folderPath, string.Format("{0}.{1}", assemblyName, assemblyExtension));
                if (!File.Exists(assemblyPath))
                {
                    assemblyExtension = "exe";
                    assemblyPath = Path.Combine(folderPath, string.Format("{0}.{1}", assemblyName, assemblyExtension));
                }

                result = Assembly.LoadFrom(assemblyPath);
            }

            return result;
        }
    }

Tags: ,

4 Comments on Adding a folder to the .NET assembly search path, to prevent the error Could not load file or assembly 'log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821' or one of its dependencies. The system cannot find the file specified.

  1. Excellent post, I refined your method a bit for my own use. Key changes I made:
    – Removed the args check because it would be a very odd situation that could cause this to run with no args, and in that case I would rather fail.
    – Changed the split to just have ‘,’ as a parameter.
    The none option is implied and then I can use the params rather an array (nothing more then it looks nicer).
    – I have added a string array for extensions so I can easy add to it later.
    – .NET 4 (which I am using) gives you args.RequestingAssembly, which tells you which assembly is trying to load the assembly. Using the Location property of that I can drop the product path stuff you used.
    – Added CultureInfo to the string.Format, as it is best practise.

    string assemblyName = args.Name.Split(‘,’)[0];
    foreach (string extension in knownExtensions)
    {
    string potentialFile = Path.Combine(Path.GetDirectoryName(args.RequestingAssembly.Location), string.Format(CultureInfo.CurrentCulture, “{0}.{1}”, assemblyName, extension));
    if (File.Exists(potentialFile))
    {
    return Assembly.LoadFrom(potentialFile);
    }
    }

    return null;

  2. HungNguyen says:

    i use visual studio 2010 (window 64bit) but when i try to drag and drop CrystalReportViewer, i have the same error. i try to use your class. but i don’t know about your MyCompany\MyProduct mean ?

  3. Ondrej Kovac says:

    I have used the RequestingAssembly property on the ResolveEventArgs, but it is always null except the first time when the event is raised.

  4. [...] the code I found at http://www.roelvanlisdonk.nl/?p=713 to programatically add their install directory to my application’s .net search path at [...]

Leave a Reply