Skip to content

ConfigurationErrorsException: <configSections> element must be the first child of the root <configuration> element #417

@gerneio

Description

@gerneio

I am using il-repack to merge outputs of a NET Framework project (using SDK-style project, if that is relevant). The NET Framework project references a few local projects. IL-repack merges the binaries just fine, however the *.exe.config is merged in a way that causes a runtime exception when executing the merged binary (from a windows service in this case):

Error StackTrace:

System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize ---> 
System.Configuration.ConfigurationErrorsException: Only one <configSections> element allowed per config file and if present must be 
the first child of the root <configuration> element. (..\MyProgram.exe.config line 26)
   at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
   at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
   at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
   --- End of inner exception stack trace ---
   at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
   at System.Configuration.ClientConfigurationSystem.PrepareClientConfigSystem(String sectionName)
   at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
   at System.Configuration.ConfigurationManager.GetSection(String sectionName)
   at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName)
   at System.Diagnostics.DiagnosticsConfiguration.GetConfigSection()
   at System.Diagnostics.DiagnosticsConfiguration.Initialize()
   at System.Diagnostics.DiagnosticsConfiguration.get_SwitchSettings()
   at System.Diagnostics.Switch.InitializeConfigSettings()
   at System.Diagnostics.Switch.InitializeWithStatus()
   at System.Diagnostics.Switch.get_SwitchSetting()
   at System.Xml.Serialization.TempAssembly.LoadGeneratedAssembly(Type type, String defaultNamespace, XmlSerializerImplementation& contract)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at MyCommon.ConnectionString.getConnectionString()
   at MyProgram.Processing.LoadSettings()
   at MyProgram.Processing.OnStart(String[] args)

The offending merged *.exe.config looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <runtime>
    ...
  </runtime>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
</configuration>

If I read the exception, it basically specifies that configSections must be the first element under the configuration root node. So if I manually change it to the below, then the application executes just fine:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <runtime>
    ...
  </runtime>
</configuration>

Note that in the unmerged config files, the configSections is part of the MyCommon.dll.config file (the dependency), which DOES have it at the top under the root node.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  ...
</configuration>

Ultimately I ended up writing a target/task that modifies the merged config file after repacking and removes the configSections node entirely (since my app worked fine w/o it), but of course the proper fix is for il-repack to be updated to order the nodes with this limitation in mind.

I did mess around with the specified assembly order (placing MyCommon.dll before MyProgram.exe), which did make a difference to the merged config file, however the resulting merged binary didn't execute successfully for my windows service. Besides, that is still kind of a hacky workaround and could fail as other dependencies are added down-the-road.

Looking over the code briefly, seems that the code responsible for merging these config files is here. Only tested from a REPL, but seems like the simplest solution would be to do something like this before saving the merged file:

...
if (firstFile.Root.Element("configSections") is not null) {
  var sortedNodes = firstFile.Root.Elements().OrderBy(e => e.Name == "configSections" ? 0 : 1);
  // OR: var sortedNodes = from c in firstFile.Root.Elements() orderby c.Name == "configSections" ? 0 : 1 select c;
  firstFile.Root.ReplaceWith(new XElement(firstFile.Root.Name, sortedNodes));
}

firstFile.Save(repack.Options.OutputFile + ".config");

Needs proper validation and testing, but in my REPL this seemed to do the job perfectly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions