Skip to content

Commit d3996bc

Browse files
authored
Resolve Assembly Conflicts (#1867)
* Use AssemblyLoadContextProxy in PS Core. * Dynamically load required dependencies. * Update required PS 5.1 dependencies
1 parent bc794fc commit d3996bc

File tree

4 files changed

+90
-69
lines changed

4 files changed

+90
-69
lines changed

src/Authentication/Authentication/Microsoft.Graph.Authentication.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<NuspecFile>Microsoft.Graph.Authentication.nuspec</NuspecFile>
1212
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
1313
<VersionPrefix>2.0.0</VersionPrefix>
14-
<VersionSuffix>preview2</VersionSuffix>
14+
<VersionSuffix>preview5</VersionSuffix>
1515
</PropertyGroup>
1616
<PropertyGroup>
1717
<EnableNETAnalyzers>true</EnableNETAnalyzers>

src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,34 @@
2424
<file src="artifacts\Microsoft.Graph.Authentication.Core.dll" />
2525
<file src="artifacts\StartupScripts\" target="StartupScripts" />
2626
<file src="artifacts\custom\" target="custom" />
27-
<file src="artifacts\Dependencies\Azure.Core.dll" target="Dependencies" />
28-
<file src="artifacts\Dependencies\Azure.Identity.dll" target="Dependencies" />
29-
<file src="artifacts\Dependencies\Newtonsoft.Json.dll" target="Dependencies" />
30-
<file src="artifacts\Dependencies\Microsoft.Bcl.AsyncInterfaces.dll" target="Dependencies" />
31-
<file src="artifacts\Dependencies\Microsoft.IdentityModel.Abstractions.dll" target="Dependencies" />
32-
<file src="artifacts\Dependencies\Microsoft.Identity.Client.Extensions.Msal.dll" target="Dependencies" />
33-
<file src="artifacts\Dependencies\System.Memory.dll" target="Dependencies" />
34-
<file src="artifacts\Dependencies\System.Threading.Tasks.Extensions.dll" target="Dependencies" />
35-
<file src="artifacts\Dependencies\System.Diagnostics.DiagnosticSource.dll" target="Dependencies" />
36-
<file src="artifacts\Dependencies\System.Runtime.CompilerServices.Unsafe.dll" target="Dependencies" />
37-
<file src="artifacts\Dependencies\System.Text.Json.dll" target="Dependencies" />
38-
<file src="artifacts\Dependencies\System.Text.Encodings.Web.dll" target="Dependencies" />
39-
<file src="artifacts\Dependencies\System.Buffers.dll" target="Dependencies" />
40-
<file src="artifacts\Dependencies\System.Numerics.Vectors.dll" target="Dependencies" />
4127
<!-- Copy framework dependent assemblies to respective framework directory. -->
4228
<!-- Core-->
29+
<file src="artifacts\Dependencies\Core\Azure.Core.dll" target="Dependencies\Core" />
30+
<file src="artifacts\Dependencies\Core\Azure.Identity.dll" target="Dependencies\Core" />
4331
<file src="artifacts\Dependencies\Core\Microsoft.Graph.Core.dll" target="Dependencies\Core" />
4432
<file src="artifacts\Dependencies\Core\Microsoft.Identity.Client.dll" target="Dependencies\Core" />
33+
<file src="artifacts\Dependencies\Core\Microsoft.Identity.Client.Extensions.Msal.dll" target="Dependencies\Core" />
34+
<file src="artifacts\Dependencies\Core\Microsoft.IdentityModel.Abstractions.dll" target="Dependencies\Core" />
35+
<file src="artifacts\Dependencies\Core\Newtonsoft.Json.dll" target="Dependencies\Core" />
4536
<file src="artifacts\Dependencies\Core\System.Security.Cryptography.ProtectedData.dll" target="Dependencies\Core" />
4637
<!-- Desktop -->
38+
<file src="artifacts\Dependencies\Desktop\Azure.Core.dll" target="Dependencies\Desktop" />
39+
<file src="artifacts\Dependencies\Desktop\Azure.Identity.dll" target="Dependencies\Desktop" />
40+
<file src="artifacts\Dependencies\Desktop\Microsoft.Bcl.AsyncInterfaces.dll" target="Dependencies\Desktop" />
4741
<file src="artifacts\Dependencies\Desktop\Microsoft.Graph.Core.dll" target="Dependencies\Desktop" />
4842
<file src="artifacts\Dependencies\Desktop\Microsoft.Identity.Client.dll" target="Dependencies\Desktop" />
49-
<file src="artifacts\Dependencies\Desktop\System.Security.Cryptography.ProtectedData.dll" target="Dependencies\Desktop" />
43+
<file src="artifacts\Dependencies\Desktop\Microsoft.Identity.Client.Extensions.Msal.dll" target="Dependencies\Desktop" />
44+
<file src="artifacts\Dependencies\Desktop\Microsoft.IdentityModel.Abstractions.dll" target="Dependencies\Desktop" />
45+
<file src="artifacts\Dependencies\Desktop\Newtonsoft.Json.dll" target="Dependencies\Desktop" />
46+
<file src="artifacts\Dependencies\Desktop\System.Buffers.dll" target="Dependencies\Desktop" />
47+
<file src="artifacts\Dependencies\Desktop\System.Diagnostics.DiagnosticSource.dll" target="Dependencies\Desktop" />
48+
<file src="artifacts\Dependencies\Desktop\System.Memory.dll" target="Dependencies\Desktop" />
5049
<file src="artifacts\Dependencies\Desktop\System.Net.Http.WinHttpHandler.dll" target="Dependencies\Desktop" />
50+
<file src="artifacts\Dependencies\Desktop\System.Numerics.Vectors.dll" target="Dependencies\Desktop" />
51+
<file src="artifacts\Dependencies\Desktop\System.Runtime.CompilerServices.Unsafe.dll" target="Dependencies\Desktop" />
52+
<file src="artifacts\Dependencies\Desktop\System.Security.Cryptography.ProtectedData.dll" target="Dependencies\Desktop" />
53+
<file src="artifacts\Dependencies\Desktop\System.Text.Encodings.Web.dll" target="Dependencies\Desktop" />
54+
<file src="artifacts\Dependencies\Desktop\System.Text.Json.dll" target="Dependencies\Desktop" />
55+
<file src="artifacts\Dependencies\Desktop\System.Threading.Tasks.Extensions.dll" target="Dependencies\Desktop" />
5156
</files>
5257
</package>

src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
# Generated by: Microsoft
55
#
6-
# Generated on: 12/19/2022
6+
# Generated on: 3/10/2023
77
#
88

99
@{
@@ -114,7 +114,7 @@ PrivateData = @{
114114
ReleaseNotes = 'See https://aka.ms/GraphPowerShell-Release.'
115115

116116
# Prerelease string of this module
117-
Prerelease = 'preview2'
117+
Prerelease = 'preview5'
118118

119119
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
120120
# RequireLicenseAcceptance = $false

src/Authentication/Authentication/Utilities/DependencyAssemblyResolver.cs

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,64 +11,43 @@ namespace Microsoft.Graph.PowerShell.Authentication.Utilities
1111
{
1212
public static class DependencyAssemblyResolver
1313
{
14+
private static readonly Assembly Self = typeof(DependencyAssemblyResolver).Assembly;
15+
private static readonly AssemblyLoadContextProxy Proxy = AssemblyLoadContextProxy.CreateLoadContext("msgraph-load-context");
16+
1417
// Catalog our dependencies here to ensure we don't load anything else.
15-
private static readonly IReadOnlyDictionary<string, Version> Dependencies = new Dictionary<string, Version>
16-
{
17-
{ "Azure.Core", new Version("1.28.0") },
18-
{ "Azure.Identity", new Version("1.8.2") },
19-
{ "Microsoft.Bcl.AsyncInterfaces", new Version("6.0.0") },
20-
{ "Microsoft.Graph.Core", new Version("2.0.15") },
21-
{ "Microsoft.Identity.Client", new Version("4.50.0") },
22-
{ "Microsoft.Identity.Client.Extensions.Msal", new Version("2.26.0") },
23-
{ "Microsoft.IdentityModel.Abstractions", new Version("6.27.0") },
24-
{ "System.Security.Cryptography.ProtectedData", new Version("7.0.1") },
25-
{ "Newtonsoft.Json", new Version("13.0.2") },
26-
{ "System.Text.Json", new Version("7.0.2") },
27-
{ "System.Text.Encodings.Web", new Version("6.0.0") },
28-
{ "System.Threading.Tasks.Extensions", new Version("4.5.4") },
29-
{ "System.Diagnostics.DiagnosticSource", new Version("4.0.4") },
30-
{ "System.Runtime.CompilerServices.Unsafe", new Version("4.0.4") },
31-
{ "System.Memory", new Version("4.0.1") },
32-
{ "System.Buffers", new Version("4.0.2") },
33-
{ "System.Numerics.Vectors", new Version("4.1.3") },
34-
{ "System.Net.Http.WinHttpHandler", new Version("6.0.0") }
35-
};
18+
private static readonly HashSet<string> Dependencies = new HashSet<string>(StringComparer.Ordinal);
3619

37-
/// <summary>
38-
/// Dependencies that need to be loaded per framework.
39-
/// </summary>
40-
private static readonly IList<string> MultiFrameworkDependencies = new List<string> {
41-
"Microsoft.Identity.Client",
42-
"System.Security.Cryptography.ProtectedData",
43-
"Microsoft.Graph.Core",
44-
"System.Net.Http.WinHttpHandler"
45-
};
20+
// Dependencies that need to be loaded per framework.
21+
private static readonly HashSet<string> MultiFrameworkDependencies = new HashSet<string>(StringComparer.Ordinal);
4622

4723
// Set up the path to our dependency directory within the module.
48-
private static readonly string DependenciesDirPath = Path.GetFullPath(Path.Combine(
49-
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Dependencies"));
24+
private static readonly string DependencyFolder = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Self.Location), "Dependencies"));
5025

5126
/// <summary>
5227
/// Framework dependency path. /Desktop for PS 5.1, and /Core for PS 6+.
5328
/// </summary>
54-
private static string FrameworkDependenciesDirPath;
29+
private static string PSEdition;
5530

5631
/// <summary>
5732
/// Initializes our custom assembly resolve event handler. This should be called on module import.
5833
/// </summary>
5934
/// <param name="isDesktopEdition"></param>
6035
public static void Initialize(bool isDesktopEdition = false)
6136
{
62-
if (isDesktopEdition)
37+
PSEdition = isDesktopEdition ? "Desktop" : "Core";
38+
39+
foreach (string filePath in Directory.EnumerateFiles(DependencyFolder, "*.dll", SearchOption.TopDirectoryOnly))
6340
{
64-
FrameworkDependenciesDirPath = "Desktop";
41+
Dependencies.Add(AssemblyName.GetAssemblyName(filePath).Name);
6542
}
66-
else
43+
44+
foreach (string filePath in Directory.EnumerateFiles(Path.Combine(DependencyFolder, PSEdition), "*.dll", SearchOption.TopDirectoryOnly))
6745
{
68-
FrameworkDependenciesDirPath = "Core";
46+
MultiFrameworkDependencies.Add(AssemblyName.GetAssemblyName(filePath).Name);
6947
}
48+
7049
// Set up our event handler when the module is loaded.
71-
AppDomain.CurrentDomain.AssemblyResolve += HandleResolveEvent;
50+
AppDomain.CurrentDomain.AssemblyResolve += ResolvingHandler;
7251
}
7352

7453
/// <summary>
@@ -78,28 +57,34 @@ public static void Initialize(bool isDesktopEdition = false)
7857
internal static void Reset()
7958
{
8059
// Remove our event hander when the module is unloaded.
81-
AppDomain.CurrentDomain.AssemblyResolve -= HandleResolveEvent;
60+
AppDomain.CurrentDomain.AssemblyResolve -= ResolvingHandler;
8261
}
8362

84-
private static Assembly HandleResolveEvent(object sender, ResolveEventArgs args)
63+
private static bool IsRequiredAssembly(AssemblyName assemblyName)
64+
{
65+
return Dependencies.Contains(assemblyName.Name) || MultiFrameworkDependencies.Contains(assemblyName.Name);
66+
}
67+
68+
private static Assembly ResolvingHandler(object sender, ResolveEventArgs args)
8569
{
8670
try
8771
{
88-
AssemblyName assemblymName = new AssemblyName(args.Name);
72+
AssemblyName assemblyName = new AssemblyName(args.Name);
8973
// We try to resolve our dependencies on our own.
90-
if (Dependencies.TryGetValue(assemblymName.Name, out Version requiredVersion)
91-
&& (requiredVersion.Major >= assemblymName.Version.Major || string.Equals(assemblymName.Name, "Newtonsoft.Json", StringComparison.OrdinalIgnoreCase)))
74+
if (IsRequiredAssembly(assemblyName))
9275
{
93-
string requiredAssemblyPath = string.Empty;
94-
if (MultiFrameworkDependencies.Contains(assemblymName.Name))
76+
string requiredAssemblyPath = MultiFrameworkDependencies.Contains(assemblyName.Name)
77+
? requiredAssemblyPath = Path.Combine(DependencyFolder, PSEdition, $"{assemblyName.Name}.dll")
78+
: requiredAssemblyPath = Path.Combine(DependencyFolder, $"{assemblyName.Name}.dll");
79+
if (File.Exists(requiredAssemblyPath))
9580
{
96-
requiredAssemblyPath = Path.Combine(DependenciesDirPath, FrameworkDependenciesDirPath, $"{assemblymName.Name}.dll");
81+
// - In .NET, load the assembly into the custom assembly load context.
82+
// - In .NET Framework, assembly conflict is not a problem, so we load the assembly
83+
// by 'Assembly.LoadFrom', the same as what powershell.exe would do.
84+
return Proxy != null
85+
? Proxy.LoadFromAssemblyPath(requiredAssemblyPath)
86+
: Assembly.LoadFrom(requiredAssemblyPath);
9787
}
98-
else
99-
{
100-
requiredAssemblyPath = Path.Combine(DependenciesDirPath, $"{assemblymName.Name}.dll");
101-
}
102-
return Assembly.LoadFile(requiredAssemblyPath);
10388
}
10489
}
10590
catch
@@ -109,4 +94,35 @@ private static Assembly HandleResolveEvent(object sender, ResolveEventArgs args)
10994
return null;
11095
}
11196
}
97+
98+
internal class AssemblyLoadContextProxy
99+
{
100+
private readonly object CustomContext;
101+
private readonly MethodInfo loadFromAssemblyPathMethod;
102+
103+
private AssemblyLoadContextProxy(Type alc, string loadContextName)
104+
{
105+
var ctor = alc.GetConstructor(new[] { typeof(string), typeof(bool) });
106+
loadFromAssemblyPathMethod = alc.GetMethod("LoadFromAssemblyPath", new[] { typeof(string) });
107+
CustomContext = ctor.Invoke(new object[] { loadContextName, false });
108+
}
109+
110+
internal Assembly LoadFromAssemblyPath(string assemblyPath)
111+
{
112+
return (Assembly)loadFromAssemblyPathMethod.Invoke(CustomContext, new[] { assemblyPath });
113+
}
114+
115+
internal static AssemblyLoadContextProxy CreateLoadContext(string name)
116+
{
117+
if (string.IsNullOrEmpty(name))
118+
{
119+
throw new ArgumentNullException(nameof(name));
120+
}
121+
122+
var alc = typeof(object).Assembly.GetType("System.Runtime.Loader.AssemblyLoadContext");
123+
return alc != null
124+
? new AssemblyLoadContextProxy(alc, name)
125+
: null;
126+
}
127+
}
112128
}

0 commit comments

Comments
 (0)