winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit cec3820ea2db1d3078b0f240d9fe4209bd76d22b
parent fa48fd8b933d2fb71bb20a21ef2952ff1b10f5a2
Author: Ruben Guerrero <rubengu@microsoft.com>
Date:   Tue, 16 May 2023 00:24:32 -0700

Microsoft.WinGet.Client throws System.InvalidOperationException: Attempt to update previously set global instance (#3253)

Microsoft.WinGet.Client throws System.InvalidOperationException:“Attempt to update previously set global instance.” when used in winget configure with a resource from Microsoft.WinGet.DSC

This is because the custom AssemblyLoadContext implemented by the client module loads WinRT.Runtime.dll into the same processes where the configuration server already have WinRT.Runtime.dll loaded. When there are multiple versions of WinRT.Runtime.dll loaded in process this exception gets thrown. More info

The fix is to always load WinRT.Runtime.dll into the default assembly context, if its not loaded by the time our handler is executed and always return null in the Load implementation of the custom ALC.

This is a problem any PowerShell module that uses cswinrt will have (if there's another one out there)
Diffstat:
Msrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs | 30++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs @@ -7,7 +7,9 @@ namespace Microsoft.WinGet.Client.Acl { using System; + using System.Collections.Generic; using System.IO; + using System.Linq; using System.Reflection; using System.Runtime.Loader; @@ -18,6 +20,14 @@ namespace Microsoft.WinGet.Client.Acl /// </summary> internal class WinGetAssemblyLoadContext : AssemblyLoadContext { + // The assemblies must be loaded in the default context. + // Loading WinRT.Runtime.dll in an ALC when is already loaded in the default context + // will result on 'Attempt to update previously set global instance.' + private static readonly IEnumerable<string> DefaultContextAssemblies = new string[] + { + @"WinRT.Runtime.dll", + }; + private static readonly string SharedDependencyPath = Path.Combine( Path.GetDirectoryName(typeof(WinGetAssemblyLoadContext).Assembly.Location), "SharedDependencies"); @@ -41,6 +51,16 @@ namespace Microsoft.WinGet.Client.Acl /// <returns>The assembly, null if not in our assembly location.</returns> internal static Assembly ResolvingHandler(AssemblyLoadContext context, AssemblyName assemblyName) { + string name = $"{assemblyName.Name}.dll"; + if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + string sharedPath = Path.Combine(SharedDependencyPath, name); + if (File.Exists(sharedPath)) + { + return AssemblyLoadContext.Default.LoadFromAssemblyPath(sharedPath); + } + } + string path = $"{Path.Combine(DirectDependencyPath, assemblyName.Name)}.dll"; if (File.Exists(path)) { @@ -53,13 +73,19 @@ namespace Microsoft.WinGet.Client.Acl /// <inheritdoc/> protected override Assembly Load(AssemblyName assemblyName) { - string path = $"{Path.Combine(SharedDependencyPath, assemblyName.Name)}.dll"; + string name = $"{assemblyName.Name}.dll"; + if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + string path = Path.Combine(SharedDependencyPath, name); if (File.Exists(path)) { return this.LoadFromAssemblyPath(path); } - path = $"{Path.Combine(DirectDependencyPath, assemblyName.Name)}.dll"; + path = Path.Combine(DirectDependencyPath, name); if (File.Exists(path)) { return this.LoadFromAssemblyPath(path);