Skip to content

Commit

Permalink
Microsoft.WinGet.Client Any CPU (#3622)
Browse files Browse the repository at this point in the history
Move to Any CPU projects
Moves Microsoft.WinGet.Client.Cmdlet and Microsoft.WinGet.Client.Engine to be Any CPU platform binaries. This is in preparation of PSResourceGet (aka PowerShellGet v3) release and addresses #3501 as $env:PROCESSOR_ARCHITECTURE is not an allowed variable in a psd1.

Handle server disconnection
Before, we had a static PackageManager object for OOP calls. In the scenario where winget is upgraded, this object will be disconnected, and one would have to close its PowerShell 7 session and open a new one to keep using the cmdlets. This PR creates a wrapper that handles disconnections.

Windows PowerShell support
Correctly throw NotSupported exception for cmdlets that use winget's COM APIs. For this I had to track down all the types being loaded per command and make sure they get loaded after the constructor of each one. This resulted in some weird cases that I need to pass a string instead of an enum in the virtual methods of some base classes.
Fix issue where Microsoft.Win32.Registry.dll was missing.
Add Pester tests specifically for Windows PowerShell

Expected Layout
Microsoft.WinGet.Client\
	Format.ps1xml
	Microsoft.WinGet.Client.psd1
	net48\
		Microsoft.Win32.Registry.dll
		Microsoft.WinGet.Client.Cmdlets.dll
		Microsoft.WinGet.Client.Engine.dll
		Microsoft.WinGet.SharedLib.dll
		Newtonsoft.Json.dll
		Octokit.dll
	net6.0-windows10.0.22000.0\
		Microsoft.WinGet.Client.Cmdlets.dll
		DirectDependencies\
			Microsoft.WinGet.Client.Engine.dll
		SharedDependencies\
			Microsoft.Windows.SDK.NET.dll
			Microsoft.WinGet.SharedLib.dll
			Newtonsoft.Json.dll
			Octokit.dll
			WinRT.Runtime.dll
			x64\
				Microsoft.Management.Deployment.dll
				Microsoft.Management.Deployment.winmd
				WindowsPackageManager.dll
				winrtact.dll
			x86\
				Microsoft.Management.Deployment.dll
				Microsoft.Management.Deployment.winmd
				WindowsPackageManager.dll
				winrtact.dll
I didn't add the native binaries in the Windows PowerShell because they will never be loaded.

Additional changes:

Remove unnecessary AnyCpu build in AppInstallerCLI.sln
Fully remove Microsoft.WinGet.Client tests from AppInstallerCLIE2ETests in favor of Pester tests.
Improve Initialize-LocalWinGetModules.ps1 for local development. You can now specify which module to initialize.
  • Loading branch information
msftrubengu committed Oct 24, 2023
1 parent a32e114 commit a10f1ab
Show file tree
Hide file tree
Showing 35 changed files with 863 additions and 919 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect.txt
Expand Up @@ -333,6 +333,7 @@ objbase
objidl
ofile
ools
oop
OPTOUT
osfhandle
Outptr
Expand Down Expand Up @@ -476,6 +477,7 @@ TOptions
TProgress
transitioning
TResult
TReturn
trimstart
TState
TStatus
Expand Down
49 changes: 43 additions & 6 deletions azure-pipelines.yml
Expand Up @@ -344,11 +344,34 @@ jobs:
condition: succeededOrFailed()

- task: CopyFiles@2
displayName: 'Copy PowerShell Module Files'
displayName: 'Copy native binaries for Microsoft.WinGet.Client'
inputs:
SourceFolder: '$(buildOutDir)\PowerShell'
TargetFolder: '$(artifactsDir)\PowerShell'
condition: succeededOrFailed()
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\net6.0-windows10.0.22000.0\SharedDependencies\$(BuildPlatform)
flattenFolders: true

- task: CopyFiles@2
displayName: 'Copy native binaries for Microsoft.WinGet.Configuration'
inputs:
SourceFolder: $(buildOutDir)
Contents: |
Microsoft.Management.Configuration\Microsoft.Management.Configuration.dll
TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform)
flattenFolders: true

- task: CopyFiles@2
displayName: 'Copy managed binaries for Microsoft.WinGet.Configuration in arch specific'
inputs:
SourceFolder: $(buildOutDirAnyCpu)
Contents: |
Microsoft.Management.Configuration.Projection\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.Projection.dll
TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform)
flattenFolders: true

- task: CopyFiles@2
displayName: 'Copy PowerShell AnyCPU Module Files'
Expand Down Expand Up @@ -444,14 +467,28 @@ jobs:

- pwsh: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\PesterTest -packageLayoutPath $(Pipeline.Workspace)\Build.x64release\DevPackage
workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\
displayName: Run Tests
displayName: Run PowerShell 7 Tests

- powershell: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\WPPesterTest
workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\
displayName: Run Windows PowerShell Tests
condition: succeededOrFailed()

- task: PublishTestResults@2
displayName: Publish Pester Test Results
displayName: Publish Pester Test Results PowerShell 7
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '$(Pipeline.Workspace)\PesterTest\Test*.xml'
failTaskOnFailedTests: true
condition: succeededOrFailed()

- task: PublishTestResults@2
displayName: Publish Pester Test Results Windows PowerShell
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '$(Pipeline.Workspace)\WPPesterTest\Test*.xml'
failTaskOnFailedTests: true
condition: succeededOrFailed()

- task: PublishPipelineArtifact@1
displayName: Publish PowerShell Module Artifacts
Expand Down
368 changes: 64 additions & 304 deletions src/AppInstallerCLI.sln

Large diffs are not rendered by default.

18 changes: 3 additions & 15 deletions src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs
Expand Up @@ -166,18 +166,6 @@ public static RunCommandResult RunCommandWithResult(string fileName, string args
return result;
}

/// <summary>
/// Run PowerShell Core command with result.
/// </summary>
/// <param name="cmdlet">Cmdlet to run.</param>
/// <param name="args">Args.</param>
/// <param name="timeOut">Optional timeout.</param>
/// <returns>Command result.</returns>
public static RunCommandResult RunPowerShellCoreCommandWithResult(string cmdlet, string args, int timeOut = 60000)
{
return RunCommandWithResult("pwsh.exe", $"-Command ipmo {TestSetup.Parameters.PowerShellModuleManifestPath}; {cmdlet} {args}", timeOut);
}

/// <summary>
/// Get test file path.
/// </summary>
Expand Down Expand Up @@ -723,7 +711,7 @@ public static void EnsureModuleState(string moduleName, bool present, string rep
ICollection<PSModuleInfo> e2eModule;
bool isPresent = false;
{
using var pwsh = new PowerShellHost(false);
using var pwsh = new PowerShellHost();
pwsh.AddModulePath($"{wingetModulePath};{customPath}");

e2eModule = pwsh.PowerShell.AddCommand("Get-Module").AddParameter("Name", moduleName).AddParameter("ListAvailable").Invoke<PSModuleInfo>();
Expand Down Expand Up @@ -763,7 +751,7 @@ public static void EnsureModuleState(string moduleName, bool present, string rep
if (location == TestModuleLocation.CurrentUser ||
location == TestModuleLocation.AllUsers)
{
using var pwsh = new PowerShellHost(false);
using var pwsh = new PowerShellHost();
pwsh.AddModulePath($"{wingetModulePath};{customPath}");
pwsh.PowerShell.AddCommand("Install-Module").AddParameter("Name", moduleName).AddParameter("Force");

Expand All @@ -788,7 +776,7 @@ public static void EnsureModuleState(string moduleName, bool present, string rep
path = wingetModulePath;
}

using var pwsh = new PowerShellHost(false);
using var pwsh = new PowerShellHost();
pwsh.AddModulePath($"{wingetModulePath};{customPath}");
pwsh.PowerShell.AddCommand("Save-Module").AddParameter("Name", moduleName).AddParameter("Path", path).AddParameter("Force");

Expand Down
6 changes: 0 additions & 6 deletions src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs
Expand Up @@ -38,7 +38,6 @@ private TestSetup()

this.StaticFileRootPath = this.InitializeDirectoryParam(Constants.StaticFileRootPathParameter, Path.GetTempPath());

this.PowerShellModuleManifestPath = this.InitializeFileParam(Constants.PowerShellModulePathParameter);
this.LocalServerCertPath = this.InitializeFileParam(Constants.LocalServerCertPathParameter);
this.PackageCertificatePath = this.InitializeFileParam(Constants.PackageCertificatePathParameter);
this.ExeInstallerPath = this.InitializeFileParam(Constants.ExeInstallerPathParameter);
Expand Down Expand Up @@ -117,11 +116,6 @@ public static TestSetup Parameters
/// </summary>
public string PackageCertificatePath { get; }

/// <summary>
/// Gets the PowerShell module path.
/// </summary>
public string PowerShellModuleManifestPath { get; }

/// <summary>
/// Gets a value indicating whether to skip creating test source.
/// </summary>
Expand Down
11 changes: 1 addition & 10 deletions src/AppInstallerCLIE2ETests/PowerShell/PowerShellHost.cs
Expand Up @@ -26,20 +26,11 @@ internal class PowerShellHost : IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="PowerShellHost"/> class.
/// </summary>
/// <param name="importModule">If to import modules.</param>
public PowerShellHost(bool importModule = true)
public PowerShellHost()
{
InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
initialSessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted;

if (importModule)
{
initialSessionState.ImportPSModule(new string[]
{
TestSetup.Parameters.PowerShellModuleManifestPath,
});
}

this.runspace = RunspaceFactory.CreateRunspace(initialSessionState);
this.runspace.Open();
this.VerifyErrorState();
Expand Down
102 changes: 0 additions & 102 deletions src/AppInstallerCLIE2ETests/PowerShell/WinGetClientModule.cs

This file was deleted.

Expand Up @@ -454,4 +454,8 @@
<Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.210505.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.210505.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.210505.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.210505.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
<!-- Rename Microsoft.Management.Deployment.InProc.dll to Microsoft.Management.Deployment.dll -->
<Target Name="RenameMicrosoftManagementDeployment" AfterTargets="AfterBuild">
<Copy SourceFiles="$(OutDir)\Microsoft.Management.Deployment.InProc.dll" DestinationFiles="$(OutDir)\Microsoft.Management.Deployment.dll" />
</Target>
</Project>
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.WinGet.Client.Acl
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;

/// <summary>
Expand All @@ -36,11 +37,22 @@ internal class WinGetAssemblyLoadContext : AssemblyLoadContext
Path.GetDirectoryName(typeof(WinGetAssemblyLoadContext).Assembly.Location),
"DirectDependencies");

private static readonly IEnumerable<Architecture> ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 };

private static readonly WinGetAssemblyLoadContext WinGetAcl = new ();

private readonly string sharedArchDependencyPath;

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());
}

/// <summary>
Expand Down Expand Up @@ -85,6 +97,12 @@ protected override Assembly Load(AssemblyName assemblyName)
return this.LoadFromAssemblyPath(path);
}

path = $"{Path.Combine(this.sharedArchDependencyPath, assemblyName.Name)}.dll";
if (File.Exists(path))
{
return this.LoadFromAssemblyPath(path);
}

path = Path.Combine(DirectDependencyPath, name);
if (File.Exists(path))
{
Expand All @@ -97,7 +115,7 @@ protected override Assembly Load(AssemblyName assemblyName)
/// <inheritdoc/>
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string path = Path.Combine(SharedDependencyPath, unmanagedDllName);
string path = Path.Combine(this.sharedArchDependencyPath, unmanagedDllName);
if (File.Exists(path))
{
return this.LoadUnmanagedDllFromPath(path);
Expand Down
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.WinGet.Client.Commands
{
using System.Management.Automation;
using System.Management.Automation;
using Microsoft.WinGet.Client.Commands.Common;
using Microsoft.WinGet.Client.Common;
using Microsoft.WinGet.Client.Engine.Commands;
Expand All @@ -23,20 +23,19 @@ public sealed class FindPackageCmdlet : FinderExtendedCmdlet
/// Searches for configured sources for packages.
/// </summary>
protected override void ProcessRecord()
{
var command = new FinderPackageCommand(
this,
this.Id,
this.Name,
this.Moniker,
this.Source,
this.Query,
this.MatchOption.ToString(),
this.Tag,
this.Command,
this.Count);

command.Find();
{
var command = new FinderPackageCommand(
this,
this.Id,
this.Name,
this.Moniker,
this.Source,
this.Query,
this.Tag,
this.Command,
this.Count);

command.Find(this.MatchOption.ToString());
}
}
}

0 comments on commit a10f1ab

Please sign in to comment.