Skip to content

Manage DLL Heck in PowerShell #269

@potatoqualitee

Description

@potatoqualitee

SQL Server is made up of a vast number of components, many managed by different teams.

Some components are being updated while others are not which leads us to DLL heck.

The way that .NET handles this is through app.config files but I don't know how to beautifully load an app.config, which looks like this

    <dependentAssembly>
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
    </dependentAssembly>

And then this:
image

That basically says "if any components call for old versions of this dll, just give them the new version instead".

You can read more here, which I found right after running into my first error after mixing and matching SMO, SqlClient and DacFx: https://stackoverflow.com/questions/62764744/could-not-load-file-or-assembly-system-runtime-compilerservices-unsafe

Right now, I solve it with this code, which is working. It had to be written in C# otherwise, I'd get a StackOverflow error in Appveyor.

if ($PSVersionTable.PSEdition -ne "Core") {
    $dir = (Join-Path $script:libraryroot "lib\net462\").Replace('\','\\')

    if (-not ("Redirector" -as [type])) {
        $source = @"
            using System;
            using System.Linq;
            using System.Reflection;
            using System.Text.RegularExpressions;

            public class Redirector
            {
                public Redirector()
                {
                    this.EventHandler = new ResolveEventHandler(AssemblyResolve);
                }

                public readonly ResolveEventHandler EventHandler;

                protected Assembly AssemblyResolve(object sender, ResolveEventArgs e)
                {
                    string[] dlls = {
                        "System.Runtime.CompilerServices.Unsafe",
                        "System.Resources.Extensions",
                        "Microsoft.SqlServer.ConnectionInfo",
                        "Microsoft.SqlServer.Smo",
                        "Microsoft.Identity.Client",
                        "System.Diagnostics.DiagnosticSource",
                        "Microsoft.IdentityModel.Abstractions",
                        "Microsoft.Data.SqlClient",
                        "System.Configuration.ConfigurationManager",
                        "Microsoft.SqlServer.Replication",
                        "Microsoft.SqlServer.Rmo"
                    };

                    var name = new AssemblyName(e.Name);
                    var assemblyName = name.Name.ToString();
                    foreach (string dll in dlls)
                    {
                        if (assemblyName == dll)
                        {
                            string filelocation = "$dir" + dll + ".dll";
                            //Console.WriteLine(filelocation);
                            return Assembly.LoadFrom(filelocation);
                        }
                    }

                    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                    {
                        // maybe this needs to change?
                        var info = assembly.GetName();
                        if (info.FullName == e.Name) {
                            return assembly;
                        }
                    }
                    return null;
                }
            }
"@

        $null = Add-Type -TypeDefinition $source
    }

    try {
        $redirector = New-Object Redirector
        [System.AppDomain]::CurrentDomain.add_AssemblyResolve($redirector.EventHandler)
    } catch {
        # unsure
    }
}

$dll =
if ($PSVersionTable.PSVersion.Major -ge 6) {
    Join-Path $script:libraryroot "lib\net6.0\dbatools.dll"
} else {
    Join-Path $script:libraryroot "lib\net462\dbatools.dll"
}

try {
    Import-Module "$dll"
} catch {
    throw "Couldn't import dbatools.dll | $PSItem"
}

if ($PSVersionTable.PSEdition -ne "Core") {
    [System.AppDomain]::CurrentDomain.remove_AssemblyResolve($onAssemblyResolveEventHandler)
}

I would also like a way to run commands (such as a disconnect) before and after specific commands without actually having to code that into there. When we talked, you mentioned this syntax which I liked.

[Reflection.AssemblyMetadata("TypeConflict", "t")]
[Reflection.AssemblyMetadata("TypeConflict", "t2")]
[Reflection.AssemblyMetadata("TypeConflict", "t3")]
param()

Thank you!

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