3

I am trying to use a DLL from our in-house .net application.

The DLL has a App.config file, and has a config section that specifies a configuration handler.

I am unable to get my PowerShell script to load this dll.

I have boiled the problem down into the simplest form I can.

Here is the PowerShel script I am trying:

[appdomain]::CurrentDomain.SetData("APP_CONFIG_FILE", "D:\CustomConfig\CustomConfig\CustomConfigTestHarness\bin\Debug\CustomConfigTestHarness.exe.config")
Add-Type -Path 'D:\CustomConfig\CustomConfig\CustomConfigTestHarness\bin\Debug\CustomConfig.dll'
$mainClass = New-Object CustomConfig.Main
$mainClass.TestConfig()

This is the config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="PrimarySqlServers" type="CustomConfig.ServerConfiguration, CustomConfig" />
    </configSections>
    <PrimarySqlServers>
        <Server name="data source=SQL\SQL2005; Initial Catalog=master;  Trusted_Connection=yes;"/>
    </PrimarySqlServers>
</configuration>

Here is the DLL:

namespace CustomConfig
{
    public class Main
    {
        public string TestEcho(string message)
        {
            return message;
        }

        public string TestConfig()
        {
            Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            string sectionName = "PrimarySqlServers";
            ServerConfiguration serverConfiguration = configuration.GetSection(sectionName) as ServerConfiguration;
            if (serverConfiguration == null || serverConfiguration.ServerList.Count == 0)
            {
                throw new ConfigurationErrorsException(string.Format(CultureInfo.InvariantCulture, "ERR_MISSING_OR_EMPTY_SECTION", sectionName));
            }
            ServerConfigurationEntry entry = serverConfiguration.ServerList[0];
            {
                return entry.Name;
            }
        }
    }
}

The configClass.cs file reads:

using System;
using System.Configuration;
using System.Diagnostics;

namespace CustomConfig
{
    /// <summary>
    /// Contains individual configuration information about a site to be deployed
    /// </summary>
    public class ServerConfiguration : ConfigurationSection
    {
        /// <summary>
        /// Get the collection of assembly items
        /// </summary>
        [ConfigurationProperty("", IsDefaultCollection = true)]
        public ServerConfigurationCollection ServerList
        {
            get { return (ServerConfigurationCollection) base[""]; }
        }
    }

    /// <summary>
    /// ContextCollection - represents the collection of context nodes
    /// </summary>
    public class ServerConfigurationCollection : ConfigurationElementCollection
    {
        /// <summary>
        /// Get the assembly item element with the given name
        /// </summary>
        /// <param name="name">The name of the item you want</param>
        /// <returns>The item specified</returns>
        public new ServerConfigurationEntry this[string name]
        {
            get
            {
                if (IndexOf(name) < 0) return null;
                return (ServerConfigurationEntry) BaseGet(name);
            }
        }

        /// <summary>
        /// Get a assembly item element by index
        /// </summary>
        /// <param name="index">The index of the item to get</param>
        /// <returns>The item specified</returns>
        public ServerConfigurationEntry this[int index]
        {
            get { return (ServerConfigurationEntry) BaseGet(index); }
        }

        /// <summary>
        /// Clear the collection of items
        /// </summary>
        public void Clear()
        {
            BaseClear();
        }

        /// <summary>
        /// Add a new item to the collection
        /// </summary>
        /// <param name="name">The name of the site to add</param>
        public void AddItem(string name)
        {
            ServerConfigurationEntry newEntry = new ServerConfigurationEntry();
            newEntry.Name = name;
            this.BaseAdd(newEntry, true);
        }


        /// <summary>
        /// Get the index of a given assembly item
        /// </summary>
        /// <param name="name">The name of the item to get the index of</param>
        /// <returns>The index of the given item, or -1 if not found</returns>
        public int IndexOf(string name)
        {
            for (int index = 0; index < base.Count; index++)
            {
                if (string.Compare(this[index].Name, name, StringComparison.OrdinalIgnoreCase) == 0)
                    return index;
            }

            return -1;
        }

        /// <summary>
        /// Get the type of collection. BasicMap in this case.
        /// </summary>
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }

        /// <summary>
        /// Factory up a new element
        /// </summary>
        /// <returns>A new element</returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new ServerConfigurationEntry();
        }

        /// <summary>
        /// Get the unique key for a assembly item element
        /// </summary>
        /// <param name="element">The element to get the key for</param>
        /// <returns>A unique identifier for the element</returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((ServerConfigurationEntry) element).Name;
        }

        /// <summary>
        /// Get the XML element name for elements of this type
        /// </summary>
        protected override string ElementName
        {
            get { return "Server"; }
        }
    }

    /// <summary>
    /// ContextElement - represents a single context element in the config
    /// </summary>
    [DebuggerDisplay("{Name}")]
    public class ServerConfigurationEntry : ConfigurationElement
    {
        private const string NAME = "name";

        /// <summary>
        /// Get or set the server name or connection string
        /// </summary>
        [ConfigurationProperty(NAME, DefaultValue = "", IsRequired = true, IsKey = true)]
        public string Name
        {
            [DebuggerStepThrough]
            get { return (string) this[NAME]; }
            [DebuggerStepThrough]
            set { this[NAME] = value; }
        }
    }
}

The error message I get when I try to run it is:

 Exception calling `"TestConfig" with "0" argument(s)`: 

 "An error occurred creating the configuration section handler for PrimarySqlServers:
 Could not load file or assembly 'CustomConfig' or one of its dependencies. The system cannot find the file specified. 
(D:\CustomConfig\CustomConfig\CustomConfigTestHarness\bin\Debug\CustomConfigTestHarness.exe.config line 4)"
    At C:\dll.ps1:15 char:1
    + $mainClass.TestConfig()
    + ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ConfigurationErrorsException
Stuart Whelan
  • 385
  • 3
  • 9

2 Answers2

3

The problem you're having is that the assembly isn't in .net's assembly search path.

You can fix that in many different ways (including putting assemblies in the GAC, etc).

It might be enough to add the rest of the information about the assembly to your section key (the version, culture, public key token ... something like this:

<section name="PrimarySqlServers" type="CustomConfig.ServerConfiguration, CustomConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 

Another way is to write an event handler:

$configdll = 'D:\CustomConfig\CustomConfig\CustomConfigTestHarness\bin\Debug\CustomConfig.dll'

[System.AppDomain]::CurrentDomain.add_AssemblyResolve({
    param($source, $assembly)
    # Detect that they're looking for YOUR assembly specifically
    if($assembly.Name.Split(",")[0] -eq "CustomConfig") {
        # And load it for them: your path will be difference
        return [System.Reflection.Assembly]::LoadFile( $configdll  )
    }
})

Another way would be to put the dll in the app's home directory. But in this case, that's your Windows\System32\WindowsPowerShell\v1.0\ ... so that's probably not a great plan.

Jaykul
  • 15,370
  • 8
  • 61
  • 70
  • 1
    A helpful note is be extremely sparing with what other references are made from within the handler block. Using `join-path` or `System.IO.Path.Combine` caused a reentry into the AssemblyResolve handler which in turn executed the statement which reentered into the handler, etc, culminating in a StackOverflowException. – Tedford Mar 08 '16 at 17:08
  • Thanks, I had the same problem and adding the rest of the information worked for me (Version=1.0.0.0, Culture=neutral, PublicKeyToken=null) – 1408786user Apr 28 '17 at 10:15
0

Well, I think that in your custom configuration section your assembly name is incorrect.

According to your post, this should be:

<section name="PrimarySqlServers" type="CustomConfig.ServerConfiguration, CustomConfigTestHarness.exe" />

The second parameter is the assembly or executable name containing the definition for your CustomConfig.ServerConfiguration type.

David Brabant
  • 41,623
  • 16
  • 83
  • 111