From 9d29f493425133bcb1c292ee0ad628ab25781074 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 29 Oct 2024 21:09:32 -0700 Subject: [PATCH 1/7] save work --- .../Helpers/AppxModuleHelper.cs | 206 +++++++++++++++--- .../Helpers/PackageDependency.cs | 23 ++ .../Helpers/WingetDependencies.cs | 20 ++ .../scripts/Initialize-LocalWinGetModules.ps1 | 2 +- 4 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageDependency.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetDependencies.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index 16fc9d5d9d..e6dc0a0478 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -9,13 +9,17 @@ namespace Microsoft.WinGet.Client.Engine.Helpers using System; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.IO; + using System.IO.Compression; using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Extensions; using Microsoft.WinGet.Common.Command; + using Newtonsoft.Json; using Octokit; using Semver; using static Microsoft.WinGet.Client.Engine.Common.Constants; @@ -63,6 +67,9 @@ internal class AppxModuleHelper // Assets private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; + private const string DependenciesAssetName = "DesktopAppInstaller_Dependencies"; + private const string DependenciesJsonName = "DesktopAppInstaller_Dependencies.json"; + private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip"; private const string License = "License1.xml"; // Dependencies @@ -74,7 +81,11 @@ internal class AppxModuleHelper private const string VCLibsUWPDesktopArm = "https://aka.ms/Microsoft.VCLibs.arm.14.00.Desktop.appx"; private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"; + private const string AppxPackageTemplate = "{0}_{1}_{2}__8wekyb3d8bbwe.appx"; + // Xaml + private const string XamlPackageName = "Microsoft.UI.Xaml.2.8"; + private const string XamlPackage28 = "Microsoft.UI.Xaml.2.8"; private const string XamlReleaseTag286 = "v2.8.6"; private const string MinimumWinGetReleaseTagForXaml28 = "v1.7.10514"; @@ -319,53 +330,68 @@ private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade, private async Task InstallDependenciesAsync(string releaseTag) { - // A better implementation would use Add-AppxPackage with -DependencyPath, but - // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter - // gets deserialized from Core the result is a single string which breaks Add-AppxPackage. - // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath - // if we are in Core, then start powershell.exe and run the same command. Right now, we just - // do Add-AppxPackage for each one. - await this.InstallVCLibsDependenciesAsync(); - await this.InstallUiXamlAsync(releaseTag); + bool result = await this.TryInstallDependenciesFromGitHubArchive(releaseTag); + + if (!result) + { + // A better implementation would use Add-AppxPackage with -DependencyPath, but + // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter + // gets deserialized from Core the result is a single string which breaks Add-AppxPackage. + // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath + // if we are in Core, then start powershell.exe and run the same command. Right now, we just + // do Add-AppxPackage for each one. + await this.InstallVCLibsDependenciesAsync(); + await this.InstallUiXamlAsync(releaseTag); + } } - private async Task InstallVCLibsDependenciesAsync() + private Dictionary GetDependenciesByArch(PackageDependency dependencies) { - var result = this.ExecuteAppxCmdlet( - GetAppxPackage, - new Dictionary - { - { Name, VCLibsUWPDesktop }, - }); + Dictionary appxPackages = new Dictionary(); + var arch = RuntimeInformation.OSArchitecture; - // See if the minimum (or greater) version is installed. - // TODO: Pull the minimum version from the target package - Version minimumVersion = new Version(VCLibsUWPDesktopVersion); + string appxPackageX64 = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "x64"); + string appxPackageX86 = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "x86"); + string appxPackageArm = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "arm"); + string appxPackageArm64 = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "arm64"); - // Construct the list of frameworks that we want present. - Dictionary vcLibsDependencies = new Dictionary(); - var arch = RuntimeInformation.OSArchitecture; if (arch == Architecture.X64) { - vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64); + appxPackages.Add("x64", appxPackageX64); } else if (arch == Architecture.X86) { - vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86); + appxPackages.Add("x86", appxPackageX86); } else if (arch == Architecture.Arm64) { // Deployment please figure out for me. - vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64); - vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86); - vcLibsDependencies.Add("arm", VCLibsUWPDesktopArm); - vcLibsDependencies.Add("arm64", VCLibsUWPDesktopArm64); + appxPackages.Add("x64", appxPackageX64); + appxPackages.Add("x86", appxPackageX86); + appxPackages.Add("arm", appxPackageArm); + appxPackages.Add("arm64", appxPackageArm64); } else { throw new PSNotSupportedException(arch.ToString()); } + return appxPackages; + } + + private void GetMissingVCLibsDependencies(Dictionary vcLibsDependencies, string requiredVersion = VCLibsUWPDesktopVersion) + { + var result = this.ExecuteAppxCmdlet( + GetAppxPackage, + new Dictionary + { + { Name, VCLibsUWPDesktop }, + }); + + // See if the minimum (or greater) version is installed. + // TODO: Pull the minimum version from the target package + Version minimumVersion = new Version(requiredVersion); + if (result != null && result.Count > 0) { @@ -402,6 +428,12 @@ private async Task InstallVCLibsDependenciesAsync() } } } + } + + private async Task InstallVCLibsDependenciesAsync() + { + Dictionary vcLibsDependencies = this.GetVCLibsDependencies(); + this.GetMissingVCLibsDependencies(vcLibsDependencies); if (vcLibsDependencies.Count != 0) { @@ -419,6 +451,126 @@ private async Task InstallVCLibsDependenciesAsync() } } + private async Task TryInstallDependenciesFromGitHubArchive(string releaseTag) + { + try + { + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + var release = await githubClient.GetReleaseAsync(releaseTag); + + var dependenciesJsonAsset = release.GetAsset(DependenciesJsonName); + if (dependenciesJsonAsset is null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"{DependenciesJsonName} asset not found on GitHub release {releaseTag}."); + return false; + } + + using var dependenciesJsonFile = new TempFile(); + await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet); + + using StreamReader r = new StreamReader(dependenciesJsonFile.FullPath); + string json = r.ReadToEnd(); + WingetDependencies? wingetDependencies = JsonConvert.DeserializeObject(json); + + if (wingetDependencies is null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to deserialize dependencies json file."); + return false; + } + + Dictionary missingDependencies = new Dictionary(); + var packageDependencies = wingetDependencies.Dependencies; + foreach (var dependency in packageDependencies) + { + if (string.Equals(dependency.Name, VCLibsUWPDesktop)) + { + Dictionary vcLibsDependencies = this.GetDependenciesByArch(dependency); + this.GetMissingVCLibsDependencies(vcLibsDependencies, dependency.Version); + + foreach (var pair in vcLibsDependencies) + { + missingDependencies.Add(pair.Key, pair.Value); + } + } + else if (string.Equals(dependency.Name, XamlPackageName)) + { + Dictionary uiXamlDependencies = this.GetDependenciesByArch(dependency); + foreach (var pair in uiXamlDependencies) + { + missingDependencies.Add(pair.Key, pair.Value); + } + } + } + + //foreach (var pair in missingDependencies) + //{ + // this.pwshCmdlet.Write(StreamType.Verbose, $"{pair.Key} and {pair.Value}"); + //} + + if (missingDependencies.Count != 0) + { + // Get all missing dependencies and install them: + using var dependenciesZipFile = new TempFile(); + using var extractedDirectory = new TempDirectory(); + var dependenciesZipAsset = release.GetAsset(DependenciesZipName); + await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet); + ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath); + + foreach (var entry in missingDependencies) + { + string fullPath = entry.Value; + if (!File.Exists(fullPath)) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); + return false; + } + + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, fullPath }, + { ErrorAction, Stop }, + }); + } + } + } + catch (WinGetRepairException) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Dependency assets not found in GitHub release."); + } + + return true; + } + + private Dictionary GetVCLibsDependencies() + { + Dictionary vcLibsDependencies = new Dictionary(); + var arch = RuntimeInformation.OSArchitecture; + if (arch == Architecture.X64) + { + vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64); + } + else if (arch == Architecture.X86) + { + vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86); + } + else if (arch == Architecture.Arm64) + { + // Deployment please figure out for me. + vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64); + vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86); + vcLibsDependencies.Add("arm", VCLibsUWPDesktopArm); + vcLibsDependencies.Add("arm64", VCLibsUWPDesktopArm64); + } + else + { + throw new PSNotSupportedException(arch.ToString()); + } + + return vcLibsDependencies; + } + private async Task InstallUiXamlAsync(string releaseTag) { (string xamlPackageName, string xamlReleaseTag) = GetXamlDependencyVersionInfo(releaseTag); diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageDependency.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageDependency.cs new file mode 100644 index 0000000000..5920afee1f --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageDependency.cs @@ -0,0 +1,23 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + /// + /// A single package dependency. + /// + internal class PackageDependency + { + /// + /// Gets or sets the name of the dependency. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the version of the dependency. + /// + public string Version { get; set; } = string.Empty; + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetDependencies.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetDependencies.cs new file mode 100644 index 0000000000..4776a6cf02 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetDependencies.cs @@ -0,0 +1,20 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Collections.Generic; + + /// + /// An object representation of the dependencies json. + /// + internal class WingetDependencies + { + /// + /// Gets or sets a list of required package dependencies. + /// + public List Dependencies { get; set; } = new List(); + } +} diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 index da7c89b1d6..14f859b56a 100644 --- a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 +++ b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -66,7 +66,7 @@ class WinGetModule [void]PrepareBinaryFiles([string] $buildRoot, [string] $config) { $copyErrors = $null - Copy-Item "$buildRoot\AnyCpu\$config\PowerShell\$($this.Name)\*" $this.Output -Force -Recurse -ErrorVariable copyErrors -ErrorAction SilentlyContinue + Copy-Item "$buildRoot\AnyCpu\$config\PowerShell\$($this.Name)" $this.Output -Force -Recurse -ErrorVariable copyErrors -ErrorAction SilentlyContinue $copyErrors | ForEach-Object { Write-Warning $_ } } From 9164c6e26808ba0a902b70321af6f8ab850acff1 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 30 Oct 2024 08:46:16 -0700 Subject: [PATCH 2/7] more edits --- .../Helpers/AppxModuleHelper.cs | 65 +++++++------------ 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index e6dc0a0478..384208343a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -379,13 +379,13 @@ private Dictionary GetDependenciesByArch(PackageDependency depen return appxPackages; } - private void GetMissingVCLibsDependencies(Dictionary vcLibsDependencies, string requiredVersion = VCLibsUWPDesktopVersion) + private void FindMissingDependencies(Dictionary dependencies, string packageName, string requiredVersion) { var result = this.ExecuteAppxCmdlet( GetAppxPackage, new Dictionary { - { Name, VCLibsUWPDesktop }, + { Name, packageName }, }); // See if the minimum (or greater) version is installed. @@ -410,21 +410,21 @@ private void GetMissingVCLibsDependencies(Dictionary vcLibsDepen string? architectureString = psobject?.Architecture?.ToString(); if (architectureString == null) { - this.pwshCmdlet.Write(StreamType.Verbose, $"VCLibs dependency has no architecture value: {psobject?.PackageFullName ?? ""}"); + this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} dependency has no architecture value: {psobject?.PackageFullName ?? ""}"); continue; } architectureString = architectureString.ToLower(); - if (vcLibsDependencies.ContainsKey(architectureString)) + if (dependencies.ContainsKey(architectureString)) { - this.pwshCmdlet.Write(StreamType.Verbose, $"VCLibs {architectureString} dependency satisfied by: {psobject?.PackageFullName ?? ""}"); - vcLibsDependencies.Remove(architectureString); + this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} {architectureString} dependency satisfied by: {psobject?.PackageFullName ?? ""}"); + dependencies.Remove(architectureString); } } else { - this.pwshCmdlet.Write(StreamType.Verbose, $"VCLibs is lower than minimum required version [{minimumVersion}]: {psobject?.PackageFullName ?? ""}"); + this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} is lower than minimum required version [{minimumVersion}]: {psobject?.PackageFullName ?? ""}"); } } } @@ -433,7 +433,7 @@ private void GetMissingVCLibsDependencies(Dictionary vcLibsDepen private async Task InstallVCLibsDependenciesAsync() { Dictionary vcLibsDependencies = this.GetVCLibsDependencies(); - this.GetMissingVCLibsDependencies(vcLibsDependencies); + this.FindMissingDependencies(vcLibsDependencies, VCLibsUWPDesktop, VCLibsUWPDesktopVersion); if (vcLibsDependencies.Count != 0) { @@ -459,11 +459,7 @@ private async Task TryInstallDependenciesFromGitHubArchive(string releaseT var release = await githubClient.GetReleaseAsync(releaseTag); var dependenciesJsonAsset = release.GetAsset(DependenciesJsonName); - if (dependenciesJsonAsset is null) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"{DependenciesJsonName} asset not found on GitHub release {releaseTag}."); - return false; - } + var dependenciesZipAsset = release.GetAsset(DependenciesZipName); using var dependenciesJsonFile = new TempFile(); await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet); @@ -478,47 +474,28 @@ private async Task TryInstallDependenciesFromGitHubArchive(string releaseT return false; } - Dictionary missingDependencies = new Dictionary(); - var packageDependencies = wingetDependencies.Dependencies; - foreach (var dependency in packageDependencies) + List missingDependencies = new List(); + foreach (var dependency in wingetDependencies.Dependencies) { - if (string.Equals(dependency.Name, VCLibsUWPDesktop)) - { - Dictionary vcLibsDependencies = this.GetDependenciesByArch(dependency); - this.GetMissingVCLibsDependencies(vcLibsDependencies, dependency.Version); + Dictionary dependenciesByArch = this.GetDependenciesByArch(dependency); + this.FindMissingDependencies(dependenciesByArch, dependency.Name, dependency.Version); - foreach (var pair in vcLibsDependencies) - { - missingDependencies.Add(pair.Key, pair.Value); - } - } - else if (string.Equals(dependency.Name, XamlPackageName)) + foreach (var pair in dependenciesByArch) { - Dictionary uiXamlDependencies = this.GetDependenciesByArch(dependency); - foreach (var pair in uiXamlDependencies) - { - missingDependencies.Add(pair.Key, pair.Value); - } + missingDependencies.Add(pair.Value); } } - //foreach (var pair in missingDependencies) - //{ - // this.pwshCmdlet.Write(StreamType.Verbose, $"{pair.Key} and {pair.Value}"); - //} - if (missingDependencies.Count != 0) { - // Get all missing dependencies and install them: using var dependenciesZipFile = new TempFile(); using var extractedDirectory = new TempDirectory(); - var dependenciesZipAsset = release.GetAsset(DependenciesZipName); await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet); ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath); foreach (var entry in missingDependencies) { - string fullPath = entry.Value; + string fullPath = entry; if (!File.Exists(fullPath)) { this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); @@ -534,13 +511,19 @@ private async Task TryInstallDependenciesFromGitHubArchive(string releaseT }); } } + + return true; } - catch (WinGetRepairException) + catch (WinGetRepairException e) { this.pwshCmdlet.Write(StreamType.Verbose, $"Dependency assets not found in GitHub release."); } + catch (Exception e) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to install dependencies from GitHub release. {e.ToString()}"); + } - return true; + return false; } private Dictionary GetVCLibsDependencies() From 28315341d89c1c2341792d95412f77d16bf8dfe5 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 30 Oct 2024 08:48:26 -0700 Subject: [PATCH 3/7] add comment --- .../Helpers/AppxModuleHelper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index 384208343a..e23710d8de 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -340,7 +340,8 @@ private async Task InstallDependenciesAsync(string releaseTag) // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath // if we are in Core, then start powershell.exe and run the same command. Right now, we just // do Add-AppxPackage for each one. - await this.InstallVCLibsDependenciesAsync(); + // This method no longer works for versions >1.9 as the vclibs url has been deprecated. + await this.InstallVCLibsDependenciesFromUriAsync(); await this.InstallUiXamlAsync(releaseTag); } } @@ -430,7 +431,7 @@ private void FindMissingDependencies(Dictionary dependencies, st } } - private async Task InstallVCLibsDependenciesAsync() + private async Task InstallVCLibsDependenciesFromUriAsync() { Dictionary vcLibsDependencies = this.GetVCLibsDependencies(); this.FindMissingDependencies(vcLibsDependencies, VCLibsUWPDesktop, VCLibsUWPDesktopVersion); From 5e9388753a47480bdde308155ad47b2e2bb82cfb Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 30 Oct 2024 15:44:34 -0700 Subject: [PATCH 4/7] it works! --- .../Commands/CliCommand.cs | 2 +- .../Commands/UserSettingsCommand.cs | 4 +- .../Commands/VersionCommand.cs | 4 +- .../Commands/WinGetPackageManagerCommand.cs | 3 +- .../Common/WinGetIntegrity.cs | 6 +- .../Extensions/ReleaseExtensions.cs | 20 ++- .../Helpers/AppxModuleHelper.cs | 128 +++++++++--------- .../Helpers/WinGetVersion.cs | 28 ++-- .../Helpers/WingetCLIWrapper.cs | 8 +- 9 files changed, 107 insertions(+), 96 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs index b089e14531..1021c3abf8 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs @@ -127,7 +127,7 @@ public void ResetAllSources() private WinGetCLICommandResult Run(string command, string parameters, int timeOut = 60000) { var wingetCliWrapper = new WingetCLIWrapper(); - var result = wingetCliWrapper.RunCommand(command, parameters, timeOut); + var result = wingetCliWrapper.RunCommand(this, command, parameters, timeOut); result.VerifyExitCode(); return result; diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs index 64433e0b6c..cec2838a01 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -42,7 +42,7 @@ public UserSettingsCommand(PSCmdlet psCmdlet) if (winGetSettingsFilePath == null) { var wingetCliWrapper = new WingetCLIWrapper(); - var settingsResult = wingetCliWrapper.RunCommand("settings", "export"); + var settingsResult = wingetCliWrapper.RunCommand(this, "settings", "export"); // Read the user settings file property. var userSettingsFile = Utilities.ConvertToHashtable(settingsResult.StdOut)["userSettingsFile"] ?? throw new ArgumentNullException("userSettingsFile"); diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs index 393ee39ee2..1e64235e7d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -30,7 +30,7 @@ public VersionCommand(PSCmdlet psCmdlet) /// public void Get() { - this.Write(StreamType.Object, WinGetVersion.InstalledWinGetVersion.TagVersion); + this.Write(StreamType.Object, WinGetVersion.InstalledWinGetVersion(this).TagVersion); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 383208c82b..16f495327a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -122,7 +122,6 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers if (seenCategories.Contains(currentCategory)) { - this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); throw; } @@ -169,7 +168,7 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force) { - var installedVersion = WinGetVersion.InstalledWinGetVersion; + var installedVersion = WinGetVersion.InstalledWinGetVersion(this); bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index cd8932e8de..9669da8dbc 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -44,7 +44,7 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers // Start by calling winget without its WindowsApp PFN path. // If it succeeds and the exit code is 0 then we are good. var wingetCliWrapper = new WingetCLIWrapper(false); - var result = wingetCliWrapper.RunCommand("--version"); + var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version"); result.VerifyExitCode(); } catch (Win32Exception e) @@ -68,7 +68,7 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers { // This assumes caller knows that the version exist. WinGetVersion expectedWinGetVersion = new WinGetVersion(expectedVersion); - var installedVersion = WinGetVersion.InstalledWinGetVersion; + var installedVersion = WinGetVersion.InstalledWinGetVersion(pwshCmdlet); if (expectedWinGetVersion.CompareTo(installedVersion) != 0) { throw new WinGetIntegrityException( diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs index 8fc5623a03..15c3c704c9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -24,16 +24,28 @@ internal static class ReleaseExtensions /// The asset. public static ReleaseAsset GetAsset(this Release release, string name) { - var assets = release.Assets.Where(a => a.Name == name); + var asset = TryGetAsset(release, name); - if (assets.Any()) + if (asset != null) { - return assets.First(); + return asset; } throw new WinGetRepairException(string.Format(Resources.ReleaseAssetNotFound, name)); } + /// + /// Gets the Asset if present. + /// + /// GitHub release. + /// Name of asset. + /// The asset, or null if not found. + public static ReleaseAsset? TryGetAsset(this Release release, string name) + { + var assets = release.Assets.Where(a => a.Name == name); + return assets.Any() ? assets.First() : null; + } + /// /// Gets the asset that ends with the string. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index e23710d8de..e5dcc9aa58 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -72,6 +72,9 @@ internal class AppxModuleHelper private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip"; private const string License = "License1.xml"; + // Format of a dependency package such as 'x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx' + private const string ExtractedDependencyPath = "{0}\\{1}_{2}_{3}.appx"; + // Dependencies // VCLibs private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; @@ -81,11 +84,7 @@ internal class AppxModuleHelper private const string VCLibsUWPDesktopArm = "https://aka.ms/Microsoft.VCLibs.arm.14.00.Desktop.appx"; private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"; - private const string AppxPackageTemplate = "{0}_{1}_{2}__8wekyb3d8bbwe.appx"; - // Xaml - private const string XamlPackageName = "Microsoft.UI.Xaml.2.8"; - private const string XamlPackage28 = "Microsoft.UI.Xaml.2.8"; private const string XamlReleaseTag286 = "v2.8.6"; private const string MinimumWinGetReleaseTagForXaml28 = "v1.7.10514"; @@ -330,7 +329,7 @@ private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade, private async Task InstallDependenciesAsync(string releaseTag) { - bool result = await this.TryInstallDependenciesFromGitHubArchive(releaseTag); + bool result = await this.InstallDependenciesFromGitHubArchive(releaseTag); if (!result) { @@ -351,10 +350,10 @@ private Dictionary GetDependenciesByArch(PackageDependency depen Dictionary appxPackages = new Dictionary(); var arch = RuntimeInformation.OSArchitecture; - string appxPackageX64 = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "x64"); - string appxPackageX86 = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "x86"); - string appxPackageArm = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "arm"); - string appxPackageArm64 = string.Format(AppxPackageTemplate, dependencies.Name, dependencies.Version, "arm64"); + string appxPackageX64 = string.Format(ExtractedDependencyPath, "x64", dependencies.Name, dependencies.Version, "x64"); + string appxPackageX86 = string.Format(ExtractedDependencyPath, "x86", dependencies.Name, dependencies.Version, "x86"); + string appxPackageArm = string.Format(ExtractedDependencyPath, "arm", dependencies.Name, dependencies.Version, "arm"); + string appxPackageArm64 = string.Format(ExtractedDependencyPath, "arm", dependencies.Name, dependencies.Version, "arm64"); if (arch == Architecture.X64) { @@ -389,8 +388,6 @@ private void FindMissingDependencies(Dictionary dependencies, st { Name, packageName }, }); - // See if the minimum (or greater) version is installed. - // TODO: Pull the minimum version from the target package Version minimumVersion = new Version(requiredVersion); if (result != null && @@ -452,79 +449,78 @@ private async Task InstallVCLibsDependenciesFromUriAsync() } } - private async Task TryInstallDependenciesFromGitHubArchive(string releaseTag) + // Returns a boolean value indicating whether dependencies were successfully installed from the GitHub release assets. + private async Task InstallDependenciesFromGitHubArchive(string releaseTag) { - try + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + var release = await githubClient.GetReleaseAsync(releaseTag); + + ReleaseAsset? dependenciesJsonAsset = release.TryGetAsset(DependenciesJsonName); + if (dependenciesJsonAsset is null) { - var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - var release = await githubClient.GetReleaseAsync(releaseTag); + return false; + } + + using var dependenciesJsonFile = new TempFile(); + await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet); - var dependenciesJsonAsset = release.GetAsset(DependenciesJsonName); - var dependenciesZipAsset = release.GetAsset(DependenciesZipName); + using StreamReader r = new StreamReader(dependenciesJsonFile.FullPath); + string json = r.ReadToEnd(); + WingetDependencies? wingetDependencies = JsonConvert.DeserializeObject(json); - using var dependenciesJsonFile = new TempFile(); - await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet); + if (wingetDependencies is null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to deserialize dependencies json file."); + return false; + } - using StreamReader r = new StreamReader(dependenciesJsonFile.FullPath); - string json = r.ReadToEnd(); - WingetDependencies? wingetDependencies = JsonConvert.DeserializeObject(json); + List missingDependencies = new List(); + foreach (var dependency in wingetDependencies.Dependencies) + { + Dictionary dependenciesByArch = this.GetDependenciesByArch(dependency); + this.FindMissingDependencies(dependenciesByArch, dependency.Name, dependency.Version); - if (wingetDependencies is null) + foreach (var pair in dependenciesByArch) { - this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to deserialize dependencies json file."); - return false; + missingDependencies.Add(pair.Value); } + } - List missingDependencies = new List(); - foreach (var dependency in wingetDependencies.Dependencies) - { - Dictionary dependenciesByArch = this.GetDependenciesByArch(dependency); - this.FindMissingDependencies(dependenciesByArch, dependency.Name, dependency.Version); + if (missingDependencies.Count != 0) + { + using var dependenciesZipFile = new TempFile(); + using var extractedDirectory = new TempDirectory(); - foreach (var pair in dependenciesByArch) - { - missingDependencies.Add(pair.Value); - } + ReleaseAsset? dependenciesZipAsset = release.TryGetAsset(DependenciesZipName); + if (dependenciesZipAsset is null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Dependencies zip asset not found on GitHub asset."); + return false; } - if (missingDependencies.Count != 0) - { - using var dependenciesZipFile = new TempFile(); - using var extractedDirectory = new TempDirectory(); - await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet); - ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath); + await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet); + ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath); - foreach (var entry in missingDependencies) + foreach (var entry in missingDependencies) + { + string fullPath = System.IO.Path.Combine(extractedDirectory.FullDirectoryPath, DependenciesAssetName, entry); + if (!File.Exists(fullPath)) { - string fullPath = entry; - if (!File.Exists(fullPath)) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); - return false; - } - - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, fullPath }, - { ErrorAction, Stop }, - }); + this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); + return false; } - } - return true; - } - catch (WinGetRepairException e) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Dependency assets not found in GitHub release."); - } - catch (Exception e) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to install dependencies from GitHub release. {e.ToString()}"); + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, fullPath }, + { ErrorAction, Stop }, + }); + } } - return false; + return true; } private Dictionary GetVCLibsDependencies() diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs index aea8657ad1..8b53c8fe47 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -7,6 +7,7 @@ namespace Microsoft.WinGet.Client.Engine.Helpers { using System; + using Microsoft.WinGet.Common.Command; /// /// WinGetVersion. Parse the string version returned by winget --version to allow comparisons. @@ -48,19 +49,6 @@ public WinGetVersion(string version) this.Version = Version.Parse(toParseVersion); } - /// - /// Gets the version of the installed winget. - /// - public static WinGetVersion InstalledWinGetVersion - { - get - { - var wingetCliWrapper = new WingetCLIWrapper(); - var result = wingetCliWrapper.RunCommand("--version"); - return new WinGetVersion(result.StdOut.Replace(Environment.NewLine, string.Empty)); - } - } - /// /// Gets the version as it appears as a tag. /// @@ -76,6 +64,18 @@ public static WinGetVersion InstalledWinGetVersion /// public bool IsPrerelease { get; } + /// + /// Gets the version of the installed winget. + /// + /// PowerShell cmdlet. + /// The WinGetVersion. + public static WinGetVersion InstalledWinGetVersion(PowerShellCmdlet pwshCmdlet) + { + var wingetCliWrapper = new WingetCLIWrapper(); + var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version"); + return new WinGetVersion(result.StdOut.Replace(Environment.NewLine, string.Empty)); + } + /// /// Version.CompareTo taking into account prerelease. /// From semver: Pre-release versions have a lower precedence than the associated normal version. diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs index 690149622e..a51d43d7da 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -11,6 +11,7 @@ namespace Microsoft.WinGet.Client.Engine.Helpers using System.IO; using Microsoft.WinGet.Client.Engine.Common; using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Common.Command; /// /// Calls winget directly. @@ -65,11 +66,12 @@ public static string WinGetFullPath /// /// Runs winget command with parameters. /// + /// PowerShell cmdlet. /// Command. /// Parameters. /// Time out. /// WinGetCommandResult. - public WinGetCLICommandResult RunCommand(string command, string? parameters = null, int timeOut = 60000) + public WinGetCLICommandResult RunCommand(PowerShellCmdlet pwshCmdlet, string command, string? parameters = null, int timeOut = 60000) { string args = command; if (!string.IsNullOrEmpty(parameters)) @@ -77,6 +79,8 @@ public WinGetCLICommandResult RunCommand(string command, string? parameters = nu args += ' ' + parameters; } + pwshCmdlet.Write(StreamType.Verbose, $"Running {this.wingetPath} with {args}"); + Process p = new () { StartInfo = new (this.wingetPath, args) From ead06d3c37fc6c8590a0d30fdea56e0b9df0cb22 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 30 Oct 2024 16:32:00 -0700 Subject: [PATCH 5/7] fix nit --- .../Helpers/AppxModuleHelper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index e5dcc9aa58..159a6635f2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -73,7 +73,7 @@ internal class AppxModuleHelper private const string License = "License1.xml"; // Format of a dependency package such as 'x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx' - private const string ExtractedDependencyPath = "{0}\\{1}_{2}_{3}.appx"; + private const string ExtractedDependencyPath = "{0}\\{1}_{2}_{0}.appx"; // Dependencies // VCLibs @@ -350,10 +350,10 @@ private Dictionary GetDependenciesByArch(PackageDependency depen Dictionary appxPackages = new Dictionary(); var arch = RuntimeInformation.OSArchitecture; - string appxPackageX64 = string.Format(ExtractedDependencyPath, "x64", dependencies.Name, dependencies.Version, "x64"); - string appxPackageX86 = string.Format(ExtractedDependencyPath, "x86", dependencies.Name, dependencies.Version, "x86"); - string appxPackageArm = string.Format(ExtractedDependencyPath, "arm", dependencies.Name, dependencies.Version, "arm"); - string appxPackageArm64 = string.Format(ExtractedDependencyPath, "arm", dependencies.Name, dependencies.Version, "arm64"); + string appxPackageX64 = string.Format(ExtractedDependencyPath, "x64", dependencies.Name, dependencies.Version); + string appxPackageX86 = string.Format(ExtractedDependencyPath, "x86", dependencies.Name, dependencies.Version); + string appxPackageArm = string.Format(ExtractedDependencyPath, "arm", dependencies.Name, dependencies.Version); + string appxPackageArm64 = string.Format(ExtractedDependencyPath, "arm", dependencies.Name, dependencies.Version); if (arch == Architecture.X64) { From 3880b59f3daa49dd8c7d25421aedaca7396e958f Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 30 Oct 2024 16:42:28 -0700 Subject: [PATCH 6/7] fix pathing --- .../Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index 159a6635f2..409d48ab55 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -67,7 +67,6 @@ internal class AppxModuleHelper // Assets private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; - private const string DependenciesAssetName = "DesktopAppInstaller_Dependencies"; private const string DependenciesJsonName = "DesktopAppInstaller_Dependencies.json"; private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip"; private const string License = "License1.xml"; @@ -503,7 +502,7 @@ private async Task InstallDependenciesFromGitHubArchive(string releaseTag) foreach (var entry in missingDependencies) { - string fullPath = System.IO.Path.Combine(extractedDirectory.FullDirectoryPath, DependenciesAssetName, entry); + string fullPath = System.IO.Path.Combine(extractedDirectory.FullDirectoryPath, entry); if (!File.Exists(fullPath)) { this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); From 7352d0b4237f369f61856459cff2fb9532d6fc3f Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 30 Oct 2024 16:44:08 -0700 Subject: [PATCH 7/7] revert --- .../Commands/WinGetPackageManagerCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 16f495327a..cdf5f16d51 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -122,6 +122,7 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers if (seenCategories.Contains(currentCategory)) { + this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); throw; }