@@ -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