-
Notifications
You must be signed in to change notification settings - Fork 222
Description
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.