Search code examples
powershellweb-configdevops

How do I add assemblyBinding bindingRedirect to web.config or app.config using powershell if does not exist


I am using PowerShell to customize deployments, I need to manually check if a web.config has a specific bindingRedirect exists for an assembly, and I need to add it in case it doesn't.

I found a lot of example and material but I struggle to both detect if the binding redirect is present and to create the actual element with nested nodes and attributes.

The web config (relevant part):

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
            <assemblyIdentity name="System.Linq" publicKeyToken="B03F5F7F11D50A3A" culture="neutral"/>
            <bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0"/>
        </dependentAssembly>
        <dependentAssembly>
            <assemblyIdentity name="System.IO" publicKeyToken="B03F5F7F11D50A3A" culture="neutral"/>
            <bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0"/>
        </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

I tried:

$XML.SelectSingleNode("/configuration/runtime/assemblyBinding[@xmlns='urn:schemas-microsoft-com:asm.v1']/dependentAssembly/assemblyIdentity[@name='System.IO']")

$XML.configuration.runtime.assemblyBinding.dependentAssembly.SelectSingleNode("assemblyIdentity[@name='System.IO']")

Select-Xml -Xml $XML -XPath '//assemblyIdentity[@name="System.IO"]'

None of the above worked to get the element to be used in an if statement.


Solution

  • After hours of trying and reading, I found that the major issue for the lookup, was the namespace which canno be specified in plain text, nor worse ignored. For the generation of the new element to be added as child node, I first tried using the creation methods for elements attribute setting and so on, but I was not succeeding in my intent, and was not very clean. So I finally figured I could create an XML from string and convert it to a node, making the code simpler and clearer.

    Here is my finding:

    [xml]$XML = Get-Content -Path $File -Raw
    $ns = New-Object System.Xml.XmlNamespaceManager($XML.NameTable)
    $ns.AddNamespace("d", $xml.configuration.runtime.assemblyBinding.NamespaceURI)
    
    if (-not ($XML.SelectSingleNode("//d:assemblyIdentity[@name='Microsoft.Extensions.Logging.Abstractions']", $ns))) {
        $loggingElement = [xml]@'
    <dependentAssembly xmlns="urn:schemas-microsoft-com:asm.v1">
      <assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
      <bindingRedirect oldVersion="0.0.0.0-2.2.0.0" newVersion="2.2.0.0" />
    </dependentAssembly>
    '@
        $XML.configuration.runtime.assemblyBinding.AppendChild($XML.ImportNode($loggingElement.dependentAssembly, $true))
    }
    $XML.Save($File)
    

    After reading the XML file, I create a namespace and I use it in SelectSingleNode. Once verified that the binding redirect is not present, I create a new XML from string and append it as a child after importing it as a node.

    Most relevant links used to find what is so far my best solution:

    Checking if content is in web.config

    Powershell: XPath cannot select when element has "xmlns" tag?

    https://mrlynchbi.wordpress.com/2014/04/02/powershell-and-xml-with-namespaces

    https://learn.microsoft.com/en-us/dotnet/standard/data/xml/xpath-queries-and-namespaces

    NOTE: this is definitely not a recommended, standard way to manage binding redirects, but in my case I use a third party application which gets shipped in zip updates, and through deployment automation I override the default web.config according to my needs.