From e70c3f8a159d8d98900a5313e344462a3ee67d74 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Date: Wed, 6 Dec 2023 11:03:00 -0800 Subject: [PATCH] Enable cmdlets for Windows PowerShell (#3951) - Enable the following cmdlets for Windows PowerShell on user context. - Find-WinGetPackage - Get-WinGetPackage - Install-WinGetPackage - Uninstall-WinGetPackage - Update-WinGetPackage - Get-WinGetSource - Move WinGetAssemblyLoadContext.cs as common code between the modules. - Implement AppDomain handler for the current AppDomain for module's dependencies. However, dependency conflicts can still occur. In order to fix it, one should implement a custom app domain and deal with the serialization boundaries. - WinRTHelpers is a static class to handle all native calls. Since AppDomain doesn't have something similar to LoadUnmanagedDll, a call to `SetDllDirectoryW` is needed before calling the native method. ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/3951) --- azure-pipelines.yml | 12 ++ .../WinGetAssemblyLoadContext.cs | 41 +++--- .../Microsoft.WinGet.Client.Cmdlets.csproj | 11 +- .../Resolver/ModuleInit.cs | 58 ++++++++ .../Resolver/WinGetAppDomain.cs | 69 ++++++++++ .../Common/ManagementDeploymentCommand.cs | 13 +- .../Helpers/ManagementDeploymentFactory.cs | 40 +----- .../Helpers/WinRTHelpers.cs | 94 +++++++++++++ .../PSObjects/PSCatalogPackage.cs | 5 +- .../Acl/CustomAssemblyLoadContext.cs | 127 ------------------ .../Acl/ModuleInit.cs | 29 ---- ...rosoft.WinGet.Configuration.Cmdlets.csproj | 4 + .../Resolver}/ModuleInit.cs | 17 ++- .../scripts/Initialize-LocalWinGetModules.ps1 | 11 +- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 43 +++--- 15 files changed, 316 insertions(+), 258 deletions(-) rename src/PowerShell/{Microsoft.WinGet.Client.Cmdlets/Acl => CommonFiles}/WinGetAssemblyLoadContext.cs (73%) create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs delete mode 100644 src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/CustomAssemblyLoadContext.cs delete mode 100644 src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/ModuleInit.cs rename src/PowerShell/{Microsoft.WinGet.Client.Cmdlets/Acl => Microsoft.WinGet.Configuration.Cmdlets/Resolver}/ModuleInit.cs (63%) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3549415c2..829c86206a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -369,6 +369,18 @@ jobs: TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net6.0-windows10.0.22000.0\SharedDependencies\$(BuildPlatform) flattenFolders: true + - task: CopyFiles@2 + displayName: 'Copy native binaries for Microsoft.WinGet.Client' + inputs: + SourceFolder: $(buildOutDir) + Contents: | + Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll + Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd + WindowsPackageManager\WindowsPackageManager.dll + UndockedRegFreeWinRT\winrtact.dll + TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net48\SharedDependencies\$(BuildPlatform) + flattenFolders: true + - task: CopyFiles@2 displayName: 'Copy native binaries for Microsoft.WinGet.Configuration' inputs: diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs b/src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs similarity index 73% rename from src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs rename to src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs index cc4edc4a24..e88ed6ba06 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs +++ b/src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------------- #if !POWERSHELL_WINDOWS -namespace Microsoft.WinGet.Client.Acl +namespace Microsoft.WinGet.Resolver { using System; using System.Collections.Generic; @@ -29,30 +29,29 @@ internal class WinGetAssemblyLoadContext : AssemblyLoadContext @"WinRT.Runtime.dll", }; - private static readonly string SharedDependencyPath = Path.Combine( - Path.GetDirectoryName(typeof(WinGetAssemblyLoadContext).Assembly.Location), - "SharedDependencies"); - - private static readonly string DirectDependencyPath = Path.Combine( - Path.GetDirectoryName(typeof(WinGetAssemblyLoadContext).Assembly.Location), - "DirectDependencies"); - - private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; + private static readonly string SharedDependencyPath; + private static readonly string SharedArchDependencyPath; + private static readonly string DirectDependencyPath; private static readonly WinGetAssemblyLoadContext WinGetAcl = new (); - private readonly string sharedArchDependencyPath; + static WinGetAssemblyLoadContext() + { + var self = typeof(WinGetAssemblyLoadContext).Assembly; + SharedDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "SharedDependencies"); + SharedArchDependencyPath = Path.Combine( + SharedDependencyPath, + RuntimeInformation.ProcessArchitecture.ToString().ToLower()); + DirectDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "DirectDependencies"); + } private WinGetAssemblyLoadContext() : base("WinGetAssemblyLoadContext", isCollectible: false) { - var arch = RuntimeInformation.ProcessArchitecture; - if (!ValidArchs.Contains(arch)) - { - throw new NotSupportedException(arch.ToString()); - } - - this.sharedArchDependencyPath = Path.Combine(SharedDependencyPath, arch.ToString().ToLower()); } /// @@ -73,7 +72,7 @@ internal static Assembly ResolvingHandler(AssemblyLoadContext context, AssemblyN } } - string path = $"{Path.Combine(DirectDependencyPath, assemblyName.Name)}.dll"; + string path = Path.Combine(DirectDependencyPath, name); if (File.Exists(path)) { return WinGetAcl.LoadFromAssemblyName(assemblyName); @@ -97,7 +96,7 @@ protected override Assembly Load(AssemblyName assemblyName) return this.LoadFromAssemblyPath(path); } - path = $"{Path.Combine(this.sharedArchDependencyPath, assemblyName.Name)}.dll"; + path = Path.Combine(SharedArchDependencyPath, name); if (File.Exists(path)) { return this.LoadFromAssemblyPath(path); @@ -115,7 +114,7 @@ protected override Assembly Load(AssemblyName assemblyName) /// protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { - string path = Path.Combine(this.sharedArchDependencyPath, unmanagedDllName); + string path = Path.Combine(SharedArchDependencyPath, unmanagedDllName); if (File.Exists(path)) { return this.LoadUnmanagedDllFromPath(path); diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj index 0d3e68303e..6b4b8419e1 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj @@ -30,6 +30,10 @@ true + + + + @@ -66,16 +70,11 @@ $(PowerShellModuleOutputDirectory)\$(TargetFramework) - + $(PowerShellModuleRuntimesDir)\SharedDependencies $(PowerShellModuleRuntimesDir)\DirectDependencies - - $(PowerShellModuleRuntimesDir) - $(PowerShellModuleRuntimesDir) - - diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs new file mode 100644 index 0000000000..4821a5954e --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Resolver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using System.Runtime.InteropServices; + +#if !POWERSHELL_WINDOWS + using System.Runtime.Loader; +#else + using Microsoft.WinGet.Client.Cmdlets.Resolver; +#endif + + /// + /// Initialization class for this module. + /// + public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup + { + private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; + + /// + public void OnImport() + { + var arch = RuntimeInformation.ProcessArchitecture; + if (!ValidArchs.Contains(arch)) + { + throw new NotSupportedException(arch.ToString()); + } + +#if !POWERSHELL_WINDOWS + AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; +#else + // If we really need to avoid dependency conflicts, we could create a custom domain and handle the serialization boundaries. + // PowerShell doesn't recommended because its complications. + AppDomain currentDomain = AppDomain.CurrentDomain; + currentDomain.AssemblyResolve += WinGetAppDomain.Handler; +#endif + } + + /// + public void OnRemove(PSModuleInfo module) + { +#if !POWERSHELL_WINDOWS + AssemblyLoadContext.Default.Resolving -= WinGetAssemblyLoadContext.ResolvingHandler; +#else + AppDomain currentDomain = AppDomain.CurrentDomain; + currentDomain.AssemblyResolve -= WinGetAppDomain.Handler; +#endif + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs new file mode 100644 index 0000000000..cb89b0a6a4 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +#if POWERSHELL_WINDOWS + +namespace Microsoft.WinGet.Client.Cmdlets.Resolver +{ + using System; + using System.IO; + using System.Reflection; + using System.Runtime.InteropServices; + + /// + /// Resolver for assemblies. + /// + internal static class WinGetAppDomain + { + private static readonly string SharedDependencyPath; + private static readonly string SharedArchDependencyPath; + private static readonly string DirectDependencyPath; + + static WinGetAppDomain() + { + var self = typeof(WinGetAppDomain).Assembly; + SharedDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "SharedDependencies"); + SharedArchDependencyPath = Path.Combine( + SharedDependencyPath, + RuntimeInformation.ProcessArchitecture.ToString().ToLower()); + DirectDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "DirectDependencies"); + } + + /// + /// Handler to register in the AppDomain. + /// + /// Source. + /// Event args. + /// The assembly if found. + public static Assembly Handler(object source, ResolveEventArgs args) + { + string name = $"{new AssemblyName(args.Name).Name}.dll"; + string path = Path.Combine(SharedDependencyPath, name); + if (File.Exists(path)) + { + return Assembly.LoadFile(path); + } + + path = Path.Combine(SharedArchDependencyPath, name); + if (File.Exists(path)) + { + return Assembly.LoadFile(path); + } + + path = Path.Combine(DirectDependencyPath, name); + if (File.Exists(path)) + { + return Assembly.LoadFile(path); + } + + return null; + } + } +} +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs index 8afcfcbd48..c00990a4a4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs @@ -9,7 +9,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands.Common using System; using System.Collections.Generic; using System.Management.Automation; - using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Common; @@ -23,9 +22,7 @@ public abstract class ManagementDeploymentCommand : BaseCommand { static ManagementDeploymentCommand() { -#if !POWERSHELL_WINDOWS - InitializeUndockedRegFreeWinRT(); -#endif + WinRTHelpers.Initialize(); } /// @@ -36,7 +33,10 @@ internal ManagementDeploymentCommand(PSCmdlet psCmdlet) : base(psCmdlet) { #if POWERSHELL_WINDOWS - throw new WindowsPowerShellNotSupported(); + if (Utilities.UsesInProcWinget) + { + throw new WindowsPowerShellNotSupported(); + } #endif } @@ -96,8 +96,5 @@ protected IReadOnlyList GetPackageCatalogReferences(str }; } } - - [DllImport("winrtact.dll", EntryPoint = "winrtact_Initialize", ExactSpelling = true, PreserveSig = true)] - private static extern void InitializeUndockedRegFreeWinRT(); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs index aed1ee4f44..48bd7fccc2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -8,9 +8,6 @@ namespace Microsoft.WinGet.Client.Engine.Helpers { using System; using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; using System.Runtime.InteropServices; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Common; @@ -167,32 +164,18 @@ private static T Create(Type? type, in Guid iid) if (Utilities.UsesInProcWinget) { - var arch = RuntimeInformation.ProcessArchitecture; - if (!ValidArchs.Contains(arch)) - { - throw new NotSupportedException(arch.ToString()); - } - - string executingAssemblyLocation = Assembly.GetExecutingAssembly().Location; - string executingAssemblyDirectory = Path.Combine(Path.GetDirectoryName(executingAssemblyLocation) !, arch.ToString().ToLower()); - - SetDllDirectoryW(executingAssemblyDirectory); - - try - { - return new T(); - } - finally - { - SetDllDirectoryW(null); - } + // This doesn't work on Windows PowerShell + // If we want to support it, we need something that loads the + // Microsoft.Management.Deployment.dll for .NET framework as CsWinRT + // does for .NET Core + return new T(); } object? instance = null; if (Utilities.ExecutingAsAdministrator) { - int hr = WinGetServerManualActivation_CreateInstance(type.GUID, iid, 0, out instance); + int hr = WinRTHelpers.ManualActivation(type.GUID, iid, 0, out instance); if (hr < 0) { @@ -223,16 +206,5 @@ private static T Create(Type? type, in Guid iid) return (T)instance; #endif } - - [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] - private static extern int WinGetServerManualActivation_CreateInstance( - [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, - [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, - uint flags, - [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SetDllDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string? directory); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs new file mode 100644 index 0000000000..1ea255813a --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs @@ -0,0 +1,94 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + + /// + /// Helper class for winrtact.dll calls. + /// + internal static class WinRTHelpers + { +#if POWERSHELL_WINDOWS + private static readonly string ArchDependencyPath; + + static WinRTHelpers() + { + ArchDependencyPath = Path.Combine( + Path.GetDirectoryName( + Path.GetDirectoryName(typeof(WinRTHelpers).Assembly.Location)), + "SharedDependencies", + RuntimeInformation.ProcessArchitecture.ToString().ToLower()); + } +#endif + + /// + /// Calls winrtact_Initialize. + /// + public static void Initialize() + { +#if POWERSHELL_WINDOWS + SetDllDirectoryW(ArchDependencyPath); + + try + { +#endif + InitializeUndockedRegFreeWinRT(); + +#if POWERSHELL_WINDOWS + } + finally + { + SetDllDirectoryW(null); + } +#endif + } + + /// + /// Calls WinGetServerManualActivation_CreateInstance. + /// + /// Class id. + /// IID. + /// Flags. + /// Out object. + /// Result of WinGetServerManualActivation_CreateInstance. + public static int ManualActivation(Guid clsid, Guid iid, uint flags, out object instance) + { +#if POWERSHELL_WINDOWS + SetDllDirectoryW(ArchDependencyPath); + + try + { +#endif + return WinGetServerManualActivation_CreateInstance(clsid, iid, flags, out instance); + +#if POWERSHELL_WINDOWS + } + finally + { + SetDllDirectoryW(null); + } +#endif + } + + [DllImport("winrtact.dll", EntryPoint = "winrtact_Initialize", ExactSpelling = true, PreserveSig = true)] + private static extern void InitializeUndockedRegFreeWinRT(); + + [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] + private static extern int WinGetServerManualActivation_CreateInstance( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, + [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, + uint flags, + [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetDllDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string? directory); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs index c01ae0b462..d35516d21e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs @@ -7,7 +7,6 @@ namespace Microsoft.WinGet.Client.Engine.PSObjects { using System.Linq; - using System.Management.Automation; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Exceptions; @@ -61,11 +60,11 @@ public bool IsUpdateAvailable /// /// Gets the source name of the catalog package. /// - public string Source + public string? Source { get { - return this.CatalogPackageCOM.DefaultInstallVersion.PackageCatalog.Info.Name; + return this.CatalogPackageCOM.DefaultInstallVersion?.PackageCatalog.Info.Name; } } diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/CustomAssemblyLoadContext.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/CustomAssemblyLoadContext.cs deleted file mode 100644 index 8f242517ef..0000000000 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/CustomAssemblyLoadContext.cs +++ /dev/null @@ -1,127 +0,0 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- -namespace Microsoft.WinGet.Configuration.Acl -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.InteropServices; - using System.Runtime.Loader; - - /// - /// Custom assembly load context for this module. - /// This helps us load our dependencies without carrying about apps importing this module. - /// All dependencies except the Engine dll needs to be under a Dependencies directory. - /// - internal class CustomAssemblyLoadContext : 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 DefaultContextAssemblies = new string[] - { - @"WinRT.Runtime.dll", - }; - - private static readonly string SharedDependencyPath = Path.Combine( - Path.GetDirectoryName(typeof(CustomAssemblyLoadContext).Assembly.Location), - "SharedDependencies"); - - private static readonly string DirectDependencyPath = Path.Combine( - Path.GetDirectoryName(typeof(CustomAssemblyLoadContext).Assembly.Location), - "DirectDependencies"); - - private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64, Architecture.Arm64 }; - - private static readonly CustomAssemblyLoadContext WinGetAcl = new (); - - private readonly string sharedArchDependencyPath; - - private CustomAssemblyLoadContext() - : base("WinGetAssemblyLoadContext", isCollectible: false) - { - var arch = RuntimeInformation.ProcessArchitecture; - - if (!ValidArchs.Contains(arch)) - { - throw new NotSupportedException(arch.ToString()); - } - - this.sharedArchDependencyPath = Path.Combine(SharedDependencyPath, arch.ToString().ToLower()); - } - - /// - /// Handler to resolve assemblies. - /// - /// Assembly load context. - /// Assembly name. - /// The assembly, null if not in our assembly location. - 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)) - { - return WinGetAcl.LoadFromAssemblyName(assemblyName); - } - - return null; - } - - /// - protected override Assembly Load(AssemblyName assemblyName) - { - string name = $"{assemblyName.Name}.dll"; - if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - return null; - } - - string path = $"{Path.Combine(SharedDependencyPath, assemblyName.Name)}.dll"; - if (File.Exists(path)) - { - return this.LoadFromAssemblyPath(path); - } - - path = $"{Path.Combine(this.sharedArchDependencyPath, assemblyName.Name)}.dll"; - if (File.Exists(path)) - { - return this.LoadFromAssemblyPath(path); - } - - path = $"{Path.Combine(DirectDependencyPath, assemblyName.Name)}.dll"; - if (File.Exists(path)) - { - return this.LoadFromAssemblyPath(path); - } - - return null; - } - - /// - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string path = Path.Combine(SharedDependencyPath, unmanagedDllName); - if (File.Exists(path)) - { - return this.LoadUnmanagedDllFromPath(path); - } - - return IntPtr.Zero; - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/ModuleInit.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/ModuleInit.cs deleted file mode 100644 index 7b03d46f5d..0000000000 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Acl/ModuleInit.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Acl -{ - using System.Management.Automation; - using System.Runtime.Loader; - - /// - /// Initialization class for this module. - /// - public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup - { - /// - public void OnImport() - { - AssemblyLoadContext.Default.Resolving += CustomAssemblyLoadContext.ResolvingHandler; - } - - /// - public void OnRemove(PSModuleInfo module) - { - AssemblyLoadContext.Default.Resolving -= CustomAssemblyLoadContext.ResolvingHandler; - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj index d3610d120b..c01cbd7fc8 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj @@ -25,6 +25,10 @@ + + + + diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/ModuleInit.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs similarity index 63% rename from src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/ModuleInit.cs rename to src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs index 4b71078e1b..db13f9a1a0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/ModuleInit.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs @@ -3,10 +3,14 @@ // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // // ----------------------------------------------------------------------------- -#if !POWERSHELL_WINDOWS -namespace Microsoft.WinGet.Client.Acl + +namespace Microsoft.WinGet.Resolver { + using System; + using System.Collections.Generic; + using System.Linq; using System.Management.Automation; + using System.Runtime.InteropServices; using System.Runtime.Loader; /// @@ -14,9 +18,17 @@ namespace Microsoft.WinGet.Client.Acl /// public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup { + private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64, Architecture.Arm64 }; + /// public void OnImport() { + var arch = RuntimeInformation.ProcessArchitecture; + if (!ValidArchs.Contains(arch)) + { + throw new NotSupportedException(arch.ToString()); + } + AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; } @@ -27,4 +39,3 @@ public void OnRemove(PSModuleInfo module) } } } -#endif \ No newline at end of file diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 index 98ab56b7f4..34ff3abebf 100644 --- a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 +++ b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -32,7 +32,10 @@ param ( $BuildRoot = "", [switch] - $SkipImportModule + $SkipImportModule, + + [switch] + $Clean ) class WinGetModule @@ -161,6 +164,11 @@ if ($BuildRoot -eq "") $BuildRoot = "$PSScriptRoot\..\.."; } +if ($Clean -and (Test-Path $moduleRootOutput)) +{ + Remove-Item $moduleRootOutput -Recurse +} + # Modules, they should be in dependency order so that when importing we don't pick up the release modules. $local:modules = @() if ($moduleToConfigure.HasFlag([ModuleType]::Client)) @@ -176,6 +184,7 @@ if ($moduleToConfigure.HasFlag([ModuleType]::Client)) "UndockedRegFreeWinRT\winrtact.dll" ) $module.AddArchSpecificFiles($additionalFiles, "net6.0-windows10.0.22000.0\SharedDependencies", $BuildRoot, $Configuration) + $module.AddArchSpecificFiles($additionalFiles, "net48\SharedDependencies", $BuildRoot, $Configuration) $modules += $module } diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index a006656e16..dcde7af990 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -46,19 +46,22 @@ BeforeAll { } } + # This is a workaround to an issue where the server takes longer than expected to terminate when + # running from PowerShell. This can cause other E2E tests to fail when attempting to reset the test source. function RemoveTestSource { - if ($PSEdition -eq "Core") - { - try { - Get-WinGetSource -Name 'TestSource' - } - catch { - # Source Remove requires admin privileges, this will only execute successfully in an elevated PowerShell. - # This is a workaround to an issue where the server takes longer than expected to terminate when - # running from PowerShell. This can cause other E2E tests to fail when attempting to reset the test source. - Start-Process -FilePath $wingetExeName -ArgumentList "source remove TestSource" + try { + # Source Remove requires admin privileges, this will only execute successfully in an elevated PowerShell. + $testSource = Get-WinGetSource | Where-Object -Property 'Name' -eq 'TestSource' + if ($null -ne $testSource) + { + # Source Remove requires admin privileges + Remove-WinGetSource -Name 'TestSource' } } + catch { + # Non-admin + Start-Process -FilePath $wingetExeName -ArgumentList "source remove TestSource" + } } function CreatePolicyKeyIfNotExists() @@ -125,7 +128,7 @@ Describe 'Get-WinGetVersion' { } } -Describe 'Get|Add|Reset-WinGetSource' -Skip:($PSEdition -eq "Desktop") { +Describe 'Get|Add|Reset-WinGetSource' { BeforeAll { AddTestSource @@ -150,7 +153,7 @@ Describe 'Get|Add|Reset-WinGetSource' -Skip:($PSEdition -eq "Desktop") { } } -Describe 'Find-WinGetPackage' -Skip:($PSEdition -eq "Desktop") { +Describe 'Find-WinGetPackage' { BeforeAll { AddTestSource @@ -203,7 +206,7 @@ Describe 'Find-WinGetPackage' -Skip:($PSEdition -eq "Desktop") { } } -Describe 'Install|Update|Uninstall-WinGetPackage' -Skip:($PSEdition -eq "Desktop") { +Describe 'Install|Update|Uninstall-WinGetPackage' { BeforeAll { AddTestSource @@ -279,7 +282,7 @@ Describe 'Install|Update|Uninstall-WinGetPackage' -Skip:($PSEdition -eq "Desktop } } -Describe 'Get-WinGetPackage' -Skip:($PSEdition -eq "Desktop") { +Describe 'Get-WinGetPackage' { BeforeAll { AddTestSource @@ -661,18 +664,6 @@ Describe 'WindowsPackageManagerServer' -Skip:($PSEdition -eq "Desktop") { } } -Describe 'WindowsPowerShell not supported' -Skip:($PSEdition -eq "Core") { - - It 'Throw not supported' { - { Find-WinGetPackage -Id "Fake.Id" } | Should -Throw "This cmdlet is not supported in Windows PowerShell." - { Get-WinGetPackage -Id "Fake.Id" } | Should -Throw "This cmdlet is not supported in Windows PowerShell." - { Install-WinGetPackage -Id "Fake.Id" } | Should -Throw "This cmdlet is not supported in Windows PowerShell." - { Uninstall-WinGetPackage -Id "Fake.Id" } | Should -Throw "This cmdlet is not supported in Windows PowerShell." - { Update-WinGetPackage -Id "Fake.Id" } | Should -Throw "This cmdlet is not supported in Windows PowerShell." - { Get-WinGetSource } | Should -Throw "This cmdlet is not supported in Windows PowerShell." - } -} - AfterAll { RemoveTestSource } \ No newline at end of file