@@ -11,64 +11,43 @@ namespace Microsoft.Graph.PowerShell.Authentication.Utilities
11
11
{
12
12
public static class DependencyAssemblyResolver
13
13
{
14
+ private static readonly Assembly Self = typeof ( DependencyAssemblyResolver ) . Assembly ;
15
+ private static readonly AssemblyLoadContextProxy Proxy = AssemblyLoadContextProxy . CreateLoadContext ( "msgraph-load-context" ) ;
16
+
14
17
// 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 ) ;
36
19
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 ) ;
46
22
47
23
// 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" ) ) ;
50
25
51
26
/// <summary>
52
27
/// Framework dependency path. /Desktop for PS 5.1, and /Core for PS 6+.
53
28
/// </summary>
54
- private static string FrameworkDependenciesDirPath ;
29
+ private static string PSEdition ;
55
30
56
31
/// <summary>
57
32
/// Initializes our custom assembly resolve event handler. This should be called on module import.
58
33
/// </summary>
59
34
/// <param name="isDesktopEdition"></param>
60
35
public static void Initialize ( bool isDesktopEdition = false )
61
36
{
62
- if ( isDesktopEdition )
37
+ PSEdition = isDesktopEdition ? "Desktop" : "Core" ;
38
+
39
+ foreach ( string filePath in Directory . EnumerateFiles ( DependencyFolder , "*.dll" , SearchOption . TopDirectoryOnly ) )
63
40
{
64
- FrameworkDependenciesDirPath = "Desktop" ;
41
+ Dependencies . Add ( AssemblyName . GetAssemblyName ( filePath ) . Name ) ;
65
42
}
66
- else
43
+
44
+ foreach ( string filePath in Directory . EnumerateFiles ( Path . Combine ( DependencyFolder , PSEdition ) , "*.dll" , SearchOption . TopDirectoryOnly ) )
67
45
{
68
- FrameworkDependenciesDirPath = "Core" ;
46
+ MultiFrameworkDependencies . Add ( AssemblyName . GetAssemblyName ( filePath ) . Name ) ;
69
47
}
48
+
70
49
// Set up our event handler when the module is loaded.
71
- AppDomain . CurrentDomain . AssemblyResolve += HandleResolveEvent ;
50
+ AppDomain . CurrentDomain . AssemblyResolve += ResolvingHandler ;
72
51
}
73
52
74
53
/// <summary>
@@ -78,28 +57,34 @@ public static void Initialize(bool isDesktopEdition = false)
78
57
internal static void Reset ( )
79
58
{
80
59
// Remove our event hander when the module is unloaded.
81
- AppDomain . CurrentDomain . AssemblyResolve -= HandleResolveEvent ;
60
+ AppDomain . CurrentDomain . AssemblyResolve -= ResolvingHandler ;
82
61
}
83
62
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 )
85
69
{
86
70
try
87
71
{
88
- AssemblyName assemblymName = new AssemblyName ( args . Name ) ;
72
+ AssemblyName assemblyName = new AssemblyName ( args . Name ) ;
89
73
// 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 ) )
92
75
{
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 ) )
95
80
{
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 ) ;
97
87
}
98
- else
99
- {
100
- requiredAssemblyPath = Path . Combine ( DependenciesDirPath , $ "{ assemblymName . Name } .dll") ;
101
- }
102
- return Assembly . LoadFile ( requiredAssemblyPath ) ;
103
88
}
104
89
}
105
90
catch
@@ -109,4 +94,35 @@ private static Assembly HandleResolveEvent(object sender, ResolveEventArgs args)
109
94
return null ;
110
95
}
111
96
}
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
+ }
112
128
}
0 commit comments