From 10167a84315fae295ee1a4a43740149f3487adea Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Thu, 29 Jun 2023 13:47:17 -0700 Subject: [PATCH 1/9] Fix win pwsh and make repair state machine --- .../Commands/WinGetPackageManagerCommand.cs | 142 +++++++----------- .../Common/WinGetIntegrity.cs | 20 --- .../Microsoft.WinGet.Client.Engine.csproj | 4 +- 3 files changed, 54 insertions(+), 112 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 722ab72ce6..0bb4934868 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -7,9 +7,11 @@ namespace Microsoft.WinGet.Client.Engine.Commands { using System; + using System.Collections.Generic; using System.Management.Automation; using Microsoft.WinGet.Client.Engine.Commands.Common; using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; using Microsoft.WinGet.Client.Engine.Properties; @@ -19,8 +21,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands public sealed class WinGetPackageManagerCommand : BaseCommand { private const string EnvPath = "env:PATH"; - private const int Succeeded = 0; - private const int Failed = -1; private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; @@ -70,95 +70,68 @@ public void RepairUsingLatest(bool preRelease) /// The expected version, if any. public void Repair(string expectedVersion) { - int result = Failed; - - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, expectedVersion); - this.PsCmdlet.WriteDebug($"Integrity category type: {integrityCategory}"); - - if (integrityCategory == IntegrityCategory.Installed || - integrityCategory == IntegrityCategory.UnexpectedVersion) - { - result = this.VerifyWinGetInstall(integrityCategory, expectedVersion); - } - else if (integrityCategory == IntegrityCategory.NotInPath) - { - this.RepairEnvPath(); - - // Now try again and get the desired winget version if needed. - var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, expectedVersion); - this.PsCmdlet.WriteDebug($"Integrity category after fixing PATH {newIntegrityCategory}"); - result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); - } - else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) - { - var appxModule = new AppxModuleHelper(this.PsCmdlet); - appxModule.RegisterAppInstaller(); - - // Now try again and get the desired winget version if needed. - var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, expectedVersion); - this.PsCmdlet.WriteDebug($"Integrity category after registering {newIntegrityCategory}"); - result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); - } - else if (integrityCategory == IntegrityCategory.AppInstallerNotInstalled || - integrityCategory == IntegrityCategory.AppInstallerNotSupported || - integrityCategory == IntegrityCategory.Failure) - { - // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGetPackageManager. - // When there is not version specified, we don't want to assume an empty version means latest, but in - // this particular case we need to. - if (string.IsNullOrEmpty(expectedVersion)) - { - var gitHubRelease = new GitHubRelease(); - expectedVersion = gitHubRelease.GetLatestVersionTagName(false); - } - - if (this.DownloadAndInstall(expectedVersion, false)) - { - result = Succeeded; - } - else - { - this.PsCmdlet.WriteDebug($"Failed installing {expectedVersion}"); - } - } - else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) - { - // Sorry, but the user has to manually enabled it. - this.PsCmdlet.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); - } - else - { - this.PsCmdlet.WriteInformation(Resources.WinGetNotSupportedMessage, WriteInformationTags); - } - - this.PsCmdlet.WriteObject(result); + this.RepairStateMachine(expectedVersion, new HashSet()); } - private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expectedVersion) + private void RepairStateMachine(string expectedVersion, HashSet seenCategories) { - if (integrityCategory == IntegrityCategory.Installed) + try { - // Nothing to do + WinGetIntegrity.AssertWinGet(this.PsCmdlet, expectedVersion); this.PsCmdlet.WriteDebug($"WinGet is in a good state."); - return Succeeded; } - else if (integrityCategory == IntegrityCategory.UnexpectedVersion) + catch (WinGetIntegrityException e) { - // The versions are different, download and install. - if (!this.InstallDifferentVersion(new WinGetVersion(expectedVersion))) + if (seenCategories.Contains(e.Category)) { - this.PsCmdlet.WriteDebug($"Failed installing {expectedVersion}"); + this.PsCmdlet.WriteDebug($"{e.Category} encountered previously"); + throw; } - else + + this.PsCmdlet.WriteDebug($"Integrity category type: {e.Category}"); + seenCategories.Add(e.Category); + + switch (e.Category) { - return Succeeded; + case IntegrityCategory.UnexpectedVersion: + this.InstallDifferentVersion(new WinGetVersion(expectedVersion)); + break; + case IntegrityCategory.NotInPath: + this.RepairEnvPath(); + break; + case IntegrityCategory.AppInstallerNotRegistered: + var appxModule = new AppxModuleHelper(this.PsCmdlet); + appxModule.RegisterAppInstaller(); + break; + case IntegrityCategory.AppInstallerNotInstalled: + case IntegrityCategory.AppInstallerNotSupported: + case IntegrityCategory.Failure: + // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGetPackageManager. + // When there is not version specified, we don't want to assume an empty version means latest, but in + // this particular case we need to. + if (string.IsNullOrEmpty(expectedVersion)) + { + var gitHubRelease = new GitHubRelease(); + expectedVersion = gitHubRelease.GetLatestVersionTagName(false); + } + + this.DownloadAndInstall(expectedVersion, false); + break; + case IntegrityCategory.AppExecutionAliasDisabled: + // Sorry, but the user has to manually enabled it. + this.PsCmdlet.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); + throw; + case IntegrityCategory.Unknown: + throw; + default: + throw new NotSupportedException(); } - } - return Failed; + this.RepairStateMachine(expectedVersion, seenCategories); + } } - private bool InstallDifferentVersion(WinGetVersion toInstallVersion) + private void InstallDifferentVersion(WinGetVersion toInstallVersion) { var installedVersion = WinGetVersion.InstalledWinGetVersion; @@ -171,10 +144,10 @@ private bool InstallDifferentVersion(WinGetVersion toInstallVersion) downgrade = true; } - return this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); + this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); } - private bool DownloadAndInstall(string versionTag, bool downgrade) + private void DownloadAndInstall(string versionTag, bool downgrade) { using var tempFile = new TempFile(); @@ -184,17 +157,6 @@ private bool DownloadAndInstall(string versionTag, bool downgrade) var appxModule = new AppxModuleHelper(this.PsCmdlet); appxModule.AddAppInstallerBundle(tempFile.FullPath, downgrade); - - // Verify that is installed - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, versionTag); - if (integrityCategory != IntegrityCategory.Installed) - { - this.PsCmdlet.WriteDebug($"Failed installing {versionTag}. IntegrityCategory after attempt: '{integrityCategory}'"); - return false; - } - - this.PsCmdlet.WriteDebug($"Installed WinGet version {versionTag}"); - return true; } private void RepairEnvPath() diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index ff9e99aabe..f964f47646 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -80,26 +80,6 @@ public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) } } - /// - /// Verifies winget runs correctly. - /// - /// The calling cmdlet. - /// Expected version. - /// Integrity category. - public static IntegrityCategory GetIntegrityCategory(PSCmdlet psCmdlet, string expectedVersion) - { - try - { - AssertWinGet(psCmdlet, expectedVersion); - } - catch (WinGetIntegrityException e) - { - return e.Category; - } - - return IntegrityCategory.Installed; - } - private static IntegrityCategory GetReason(PSCmdlet psCmdlet) { // Ok, so you are here because calling winget --version failed. Lets try to figure out why. diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj index 0930d8692b..c00d94f95c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj @@ -22,7 +22,7 @@ - USE_PROD_CLSIDS + $(DefineConstants);USE_PROD_CLSIDS @@ -118,7 +118,7 @@ - POWERSHELL_WINDOWS + $(DefineConstants);POWERSHELL_WINDOWS From 56084bf23f09b2cfcc6f4e99ea5e7d41d211e005 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Thu, 6 Jul 2023 13:40:56 -0700 Subject: [PATCH 2/9] Dowload xaml --- .../Helpers/AppxModuleHelper.cs | 45 ++++++- .../Helpers/TempDirectory.cs | 118 ++++++++++++++++++ 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index 582e955fb9..87a2f29912 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -8,6 +8,7 @@ namespace Microsoft.WinGet.Client.Engine.Helpers { using System.Collections.Generic; using System.Collections.ObjectModel; + using System.IO.Compression; using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; @@ -54,6 +55,8 @@ internal class AppxModuleHelper private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"; private const string UiXaml27 = "Microsoft.UI.Xaml.2.7"; + private const string UiXamlNuget = "https://globalcdn.nuget.org/packages/microsoft.ui.xaml.2.7.3.nupkg"; + private const string UiXamlNugetAppxPathFormat = @"{0}\tools\AppX\{1}\Release\Microsoft.UI.Xaml.2.7.appx"; private readonly PSCmdlet psCmdlet; @@ -246,7 +249,45 @@ private void InstallUiXaml() var uiXamlObjs = this.GetAppxObject(UiXaml27); if (uiXamlObjs is null) { - throw new PSNotImplementedException(Resources.MicrosoftUIXaml27Message); + // Download xaml nuget, extract and install. + this.psCmdlet.WriteDebug("Downloading and installing Microsoft.UI.Xaml.2.7"); + using var tempFile = new TempFile(); + var githubRelease = new GitHubRelease(); + githubRelease.DownloadUrl(UiXamlNuget, tempFile.FullPath); + + using var tempDir = new TempDirectory(); + ZipFile.ExtractToDirectory(tempFile.FullPath, tempDir.FullDirectoryPath); + + var packagesToInstall = new List(); + + var arch = RuntimeInformation.OSArchitecture; + if (arch == Architecture.X64 || + arch == Architecture.X86) + { + packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, arch)); + } + else if (arch == Architecture.Arm64) + { + packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, Architecture.X64)); + packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, Architecture.X86)); + packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, Architecture.Arm)); + packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, arch)); + } + else + { + throw new PSNotSupportedException(arch.ToString()); + } + + foreach (var package in packagesToInstall) + { + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, package }, + { ErrorAction, Stop }, + }); + } } } @@ -280,7 +321,7 @@ private void AddAppxPackageAsUri(string packageUri) private void DownloadPackageAndAdd(string packageUrl) { - var tempFile = new TempFile(); + using var tempFile = new TempFile(); // This is weird but easy. var githubRelease = new GitHubRelease(); diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs new file mode 100644 index 0000000000..b9a94bc26b --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs @@ -0,0 +1,118 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.IO; + + /// + /// Creates a temporary directory in the user's temporary directory. + /// + internal class TempDirectory : IDisposable + { + private readonly bool cleanup; + private bool disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// Optional directory name. If null, creates a random directory name. + /// Delete directory if already exists. Default true. + /// Deletes directory at disposing time. Default true. + public TempDirectory( + string directoryName = null, + bool deleteIfExists = true, + bool cleanup = true) + { + if (directoryName is null) + { + this.DirectoryName = Path.GetRandomFileName(); + } + else + { + this.DirectoryName = directoryName; + } + + this.FullDirectoryPath = Path.Combine(Path.GetTempPath(), this.DirectoryName); + + if (deleteIfExists && Directory.Exists(this.FullDirectoryPath)) + { + Directory.Delete(this.FullDirectoryPath, true); + } + + Directory.CreateDirectory(this.FullDirectoryPath); + this.cleanup = cleanup; + } + + /// + /// Gets the directory name. + /// + public string DirectoryName { get; } + + /// + /// Gets the full directory name. + /// + public string FullDirectoryPath { get; } + + /// + /// IDisposable.Dispose . + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Copies all contents of a directory into this directory. + /// + /// Source directory. + public void CopyDirectory(string sourceDir) + { + this.CopyDirectory(sourceDir, this.FullDirectoryPath); + } + + /// + /// Protected disposed. + /// + /// Disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (this.cleanup && Directory.Exists(this.FullDirectoryPath)) + { + Directory.Delete(this.FullDirectoryPath, true); + } + + this.disposed = true; + } + } + + private void CopyDirectory(string sourceDir, string destinationDir) + { + var dir = new DirectoryInfo(sourceDir); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException(dir.FullName); + } + + Directory.CreateDirectory(destinationDir); + + foreach (FileInfo file in dir.GetFiles()) + { + file.CopyTo(Path.Combine(destinationDir, file.Name)); + } + + foreach (DirectoryInfo subDir in dir.GetDirectories()) + { + this.CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name)); + } + } + } +} From ca660ee14ce74b63f3e4f8732e558d796e461c69 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Thu, 6 Jul 2023 15:22:03 -0700 Subject: [PATCH 3/9] Assert license --- .../Common/IntegrityCategory.cs | 5 +++++ .../Common/WinGetIntegrity.cs | 13 +++++++++++++ .../Exceptions/WinGetIntegrityException.cs | 1 + .../Properties/Resources.Designer.cs | 11 ++++++++++- .../Properties/Resources.resx | 3 +++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs index aedb573190..b026070109 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs @@ -60,5 +60,10 @@ public enum IntegrityCategory /// Installed App Installer package is not supported. /// AppInstallerNotSupported, + + /// + /// No applicable license found. + /// + AppInstallerNoLicense, } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index f964f47646..b329a1ea08 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -10,6 +10,7 @@ namespace Microsoft.WinGet.Client.Engine.Common using System.ComponentModel; using System.IO; using System.Management.Automation; + using System.Runtime.CompilerServices; using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; using Microsoft.WinGet.Client.Engine.Properties; @@ -133,6 +134,18 @@ private static IntegrityCategory GetReason(PSCmdlet psCmdlet) return IntegrityCategory.AppInstallerNotSupported; } + // PowerShell is nice enough to tell us about the license. + try + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("winget").Invoke(); + } + catch (ApplicationFailedException e) + { + psCmdlet.WriteDebug(e.Message); + return IntegrityCategory.AppInstallerNoLicense; + } + // If we get here, we know the package is in the machine but not registered for the user. return IntegrityCategory.AppInstallerNotRegistered; } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs index 6b07d46bd1..fe716d172d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs @@ -62,6 +62,7 @@ public WinGetIntegrityException(IntegrityCategory category, string message) IntegrityCategory.AppInstallerNotInstalled => Resources.IntegrityAppInstallerNotInstalledMessage, IntegrityCategory.AppInstallerNotRegistered => Resources.IntegrityAppInstallerNotRegisteredMessage, IntegrityCategory.AppInstallerNotSupported => Resources.IntegrityAppInstallerNotSupportedMessage, + IntegrityCategory.AppInstallerNoLicense => Resources.IntegrityAppInstallerLicense, _ => Resources.IntegrityUnknownMessage, }; } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs index 5eb99f1123..baa0d412e4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -96,6 +96,15 @@ internal static string IntegrityAppExecutionAliasDisabledMessage { } } + /// + /// Looks up a localized string similar to No applicable license found.. + /// + internal static string IntegrityAppInstallerLicense { + get { + return ResourceManager.GetString("IntegrityAppInstallerLicense", resourceCulture); + } + } + /// /// Looks up a localized string similar to The App Installer is not installed.. /// @@ -259,7 +268,7 @@ internal static string VagueCriteriaExceptionMessage { } /// - /// Looks up a localized string similar to This cmdlet is no supported in Windows PowerShell. + /// Looks up a localized string similar to This cmdlet is not supported in Windows PowerShell.. /// internal static string WindowsPowerShellNotSupported { get { diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx index 02cdf2f628..5414cfeb99 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -207,4 +207,7 @@ This cmdlet is not supported in Windows PowerShell. + + No applicable license found. + \ No newline at end of file From 739af173a09880b917ae8ed18205101e7536d806 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Thu, 6 Jul 2023 17:47:00 -0700 Subject: [PATCH 4/9] TODO: exception and messages --- .../RepairWinGetPackageManagerCmdlet.cs | 10 +- .../Commands/WinGetPackageManagerCommand.cs | 66 +++++---- .../Common/WinGetIntegrity.cs | 32 +++-- .../Helpers/AppxModuleHelper.cs | 131 ++++++++++++++---- .../Helpers/GitHubRelease.cs | 29 +++- 5 files changed, 196 insertions(+), 72 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs index d080513cd7..9298d76fbf 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs @@ -21,6 +21,12 @@ namespace Microsoft.WinGet.Client.Commands [OutputType(typeof(int))] public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet { + /// + /// Gets or sets a value indicating whether to repair for all users. Requires admin. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllUsers { get; set; } + /// /// Attempts to repair winget. /// TODO: consider WhatIf and Confirm options. @@ -30,11 +36,11 @@ protected override void ProcessRecord() var command = new WinGetPackageManagerCommand(this); if (this.ParameterSetName == Constants.IntegrityLatestSet) { - command.RepairUsingLatest(this.IncludePreRelease.ToBool()); + command.RepairUsingLatest(this.IncludePreRelease.ToBool(), this.AllUsers.ToBool()); } else { - command.Repair(this.Version); + command.Repair(this.Version, this.AllUsers.ToBool()); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 0bb4934868..4d8c732ea5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -57,23 +57,25 @@ public void Assert(string expectedVersion) /// Repairs winget using the latest version on winget-cli. /// /// Use prerelease version on GitHub. - public void RepairUsingLatest(bool preRelease) + /// Install for all users. Requires admin. + public void RepairUsingLatest(bool preRelease, bool allUsers) { var gitHubRelease = new GitHubRelease(); string expectedVersion = gitHubRelease.GetLatestVersionTagName(preRelease); - this.Repair(expectedVersion); + this.Repair(expectedVersion, allUsers); } /// /// Repairs winget if needed. /// /// The expected version, if any. - public void Repair(string expectedVersion) + /// Install for all users. Requires admin. + public void Repair(string expectedVersion, bool allUsers) { - this.RepairStateMachine(expectedVersion, new HashSet()); + this.RepairStateMachine(expectedVersion, allUsers, new HashSet()); } - private void RepairStateMachine(string expectedVersion, HashSet seenCategories) + private void RepairStateMachine(string expectedVersion, bool allUsers, HashSet seenCategories) { try { @@ -94,28 +96,27 @@ private void RepairStateMachine(string expectedVersion, HashSet 0) { - downgrade = true; + appxModule.InstallDowngrade(toInstallVersion.TagVersion, allUsers); + } + else + { + appxModule.Install(toInstallVersion.TagVersion, allUsers); } - - this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); } - private void DownloadAndInstall(string versionTag, bool downgrade) + private void Install(string expectedVersion, bool allUsers) { - using var tempFile = new TempFile(); + // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGetPackageManager. + // When there is not version specified, we don't want to assume an empty version means latest, but in + // this particular case we need to. + if (string.IsNullOrEmpty(expectedVersion)) + { + var gitHubRelease = new GitHubRelease(); + expectedVersion = gitHubRelease.GetLatestVersionTagName(false); + } - // Download and install. - var gitHubRelease = new GitHubRelease(); - gitHubRelease.DownloadRelease(versionTag, tempFile.FullPath); + var appxModule = new AppxModuleHelper(this.PsCmdlet); + appxModule.Install(expectedVersion, allUsers); + } + private void Register() + { var appxModule = new AppxModuleHelper(this.PsCmdlet); - appxModule.AddAppInstallerBundle(tempFile.FullPath, downgrade); + appxModule.RegisterAppInstaller(); } private void RepairEnvPath() diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index b329a1ea08..d6e7204923 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -10,7 +10,6 @@ namespace Microsoft.WinGet.Client.Engine.Common using System.ComponentModel; using System.IO; using System.Management.Automation; - using System.Runtime.CompilerServices; using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; using Microsoft.WinGet.Client.Engine.Properties; @@ -85,6 +84,25 @@ private static IntegrityCategory GetReason(PSCmdlet psCmdlet) { // Ok, so you are here because calling winget --version failed. Lets try to figure out why. + // When running winget.exe on PowerShell the message of the Win32Exception will distinguish between + // The system cannot find the file specified and No applicable app licenses found but of course + // the HRESULT is the same (E_FAIL). + // To not compare strings let Powershell handle it. If calling winget throws an + // ApplicationFailedException then is most likely that the license is not there. + try + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("winget").Invoke(); + } + catch (ApplicationFailedException e) + { + psCmdlet.WriteDebug(e.Message); + return IntegrityCategory.AppInstallerNoLicense; + } + catch (Exception) + { + } + // First lets check if the file is there, which means it is installed or someone is taking our place. if (File.Exists(WingetCLIWrapper.WinGetFullPath)) { @@ -134,18 +152,6 @@ private static IntegrityCategory GetReason(PSCmdlet psCmdlet) return IntegrityCategory.AppInstallerNotSupported; } - // PowerShell is nice enough to tell us about the license. - try - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - ps.AddCommand("winget").Invoke(); - } - catch (ApplicationFailedException e) - { - psCmdlet.WriteDebug(e.Message); - return IntegrityCategory.AppInstallerNoLicense; - } - // If we get here, we know the package is in the machine but not registered for the user. return IntegrityCategory.AppInstallerNotRegistered; } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index 87a2f29912..1ddc84f490 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -100,42 +100,35 @@ public string GetAppInstallerPropertyValue(string propertyName) } /// - /// Calls Add-AppxPackage with the specified path. + /// Installs AppInstaller froma GitHub release. + /// If allUsers is true uses Add-AppxProvisionedPackage otherwise Add-AppxPacakge. /// - /// The path of the package to add. - /// If the package version is lower than the installed one. - public void AddAppInstallerBundle(string localPath, bool downgrade = false) + /// Version tag of GitHub release. + /// If install for all users is needed. + public void Install(string versionTag, bool allUsers) { - // 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. - this.InstallVCLibsDependencies(); - this.InstallUiXaml(); - - var options = new List(); - if (downgrade) + if (allUsers) { - options.Add(ForceUpdateFromAnyVersion); + this.AddProvisionPackage(versionTag); } - - try + else { - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, localPath }, - { ErrorAction, Stop }, - }, - options); + this.AddAppInstallerBundle(versionTag, false); } - catch (RuntimeException e) + } + + /// + /// Downgrades app installer. + /// + /// Version tag of GitHub release. + /// If install for all users is needed. + public void InstallDowngrade(string versionTag, bool allUsers) + { + this.AddAppInstallerBundle(versionTag, true); + + if (allUsers) { - this.psCmdlet.WriteError(e.ErrorRecord); - throw e; + // TODO: what happen in add-provisioned with downgrade? } } @@ -163,6 +156,86 @@ public void RegisterAppInstaller() }); } + private void AddProvisionPackage(string versionTag) + { + // TODO: verify system. + if (!Utilities.ExecutingAsAdministrator) + { + // Add-AppxProvisionedPackage + throw new System.Exception("Admin bro"); + } + + using var bundle = new TempFile(); + using var licenseFile = new TempFile(); + + var gitHubRelease = new GitHubRelease(); + gitHubRelease.DownloadRelease(versionTag, bundle.FullPath); + gitHubRelease.DownloadLicense(versionTag, licenseFile.FullPath); + + this.VerifyDependencies(); + + try + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("Add-AppxProvisionedPackage") + .AddParameter("Online") + .AddParameter("PackagePath", bundle.FullPath) + .AddParameter("LicensePath", licenseFile.FullPath) + .AddParameter(ErrorAction, Stop) + .Invoke(); + } + catch (RuntimeException e) + { + this.psCmdlet.WriteError(e.ErrorRecord); + throw e; + } + } + + private void AddAppInstallerBundle(string versionTag, bool downgrade = false) + { + using var bundle = new TempFile(); + + var gitHubRelease = new GitHubRelease(); + gitHubRelease.DownloadRelease(versionTag, bundle.FullPath); + + this.VerifyDependencies(); + + var options = new List(); + if (downgrade) + { + options.Add(ForceUpdateFromAnyVersion); + } + + try + { + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, bundle.FullPath }, + { ErrorAction, Stop }, + }, + options); + } + catch (RuntimeException e) + { + this.psCmdlet.WriteError(e.ErrorRecord); + throw e; + } + } + + private void VerifyDependencies() + { + // 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. + this.InstallVCLibsDependencies(); + this.InstallUiXaml(); + } + private PSObject GetAppxObject(string packageName) { return this.ExecuteAppxCmdlet( diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs index b8da873beb..a5abea9939 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs @@ -24,6 +24,7 @@ internal class GitHubRelease private const string UserAgent = "winget-powershell"; private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; private const string ContentType = "application/octet-stream"; + private const string License = "License1.xml"; private readonly IGitHubClient gitHubClient; @@ -36,7 +37,7 @@ public GitHubRelease() } /// - /// Download a release from winget-cli. + /// Download a release msixbundle from winget-cli. /// /// Optional release name. If null, gets latest. /// Output file. @@ -45,6 +46,16 @@ public void DownloadRelease(string releaseTag, string outputFile) this.DownloadReleaseAsync(releaseTag, outputFile).GetAwaiter().GetResult(); } + /// + /// Download a release license file from winget-cli. + /// + /// Optional release name. If null, gets latest. + /// Output file. + public void DownloadLicense(string releaseTag, string outputFile) + { + this.DownloadLicenseAsync(releaseTag, outputFile).GetAwaiter().GetResult(); + } + /// /// Gets the latest released version and waits. /// @@ -81,6 +92,22 @@ public async Task DownloadReleaseAsync(string releaseTag, string outputFile) await this.DownloadUrlAsync(msixBundleAsset.Url, outputFile); } + /// + /// Downloads the license xml file from the release. + /// + /// Release tag. + /// Output file. + /// A representing the asynchronous operation. + public async Task DownloadLicenseAsync(string releaseTag, string outputFile) + { + Release release = await this.gitHubClient.Repository.Release.Get(Owner, Repo, releaseTag); + + // Get asset and download. + var licenseAsset = release.Assets.Where(a => a.Name.EndsWith(License)).First(); + + await this.DownloadUrlAsync(licenseAsset.Url, outputFile); + } + /// /// Downloads a file from a url. /// From be44ccb72915e503250d3699ba7ad76f126f5fdf Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Fri, 7 Jul 2023 14:05:56 -0700 Subject: [PATCH 5/9] Verify downgrade with AllUsers --- .../Commands/WinGetPackageManagerCommand.cs | 26 ++---- .../Common/WinGetIntegrity.cs | 2 +- .../Helpers/AppxModuleHelper.cs | 93 +++++++++---------- 3 files changed, 53 insertions(+), 68 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 4d8c732ea5..19e4aa7918 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -135,34 +135,28 @@ private void RepairStateMachine(string expectedVersion, bool allUsers, HashSet 0; - this.PsCmdlet.WriteDebug($"Installed WinGet version {installedVersion.TagVersion}"); - this.PsCmdlet.WriteDebug($"Installing WinGet version {toInstallVersion.TagVersion}"); - + this.PsCmdlet.WriteDebug($"Installed WinGet version '{installedVersion.TagVersion}' " + + $"Installing WinGet version '{toInstallVersion.TagVersion}' " + + $"Is downgrade {isDowngrade}"); var appxModule = new AppxModuleHelper(this.PsCmdlet); - if (installedVersion.CompareAsDeployment(toInstallVersion) > 0) - { - appxModule.InstallDowngrade(toInstallVersion.TagVersion, allUsers); - } - else - { - appxModule.Install(toInstallVersion.TagVersion, allUsers); - } + appxModule.InstallFromGitHubRelease(toInstallVersion.TagVersion, allUsers, isDowngrade); } - private void Install(string expectedVersion, bool allUsers) + private void Install(string toInstallVersion, bool allUsers) { - // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGetPackageManager. + // If we are here and toInstallVersion is empty, it means that they just ran Repair-WinGetPackageManager. // When there is not version specified, we don't want to assume an empty version means latest, but in // this particular case we need to. - if (string.IsNullOrEmpty(expectedVersion)) + if (string.IsNullOrEmpty(toInstallVersion)) { var gitHubRelease = new GitHubRelease(); - expectedVersion = gitHubRelease.GetLatestVersionTagName(false); + toInstallVersion = gitHubRelease.GetLatestVersionTagName(false); } var appxModule = new AppxModuleHelper(this.PsCmdlet); - appxModule.Install(expectedVersion, allUsers); + appxModule.InstallFromGitHubRelease(toInstallVersion, allUsers, false); } private void Register() diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index d6e7204923..2958c1b3c6 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -74,7 +74,7 @@ public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) IntegrityCategory.UnexpectedVersion, string.Format( Resources.IntegrityUnexpectedVersionMessage, - installedVersion, + installedVersion.TagVersion, expectedVersion)); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index 1ddc84f490..fb88da8781 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -99,39 +99,6 @@ public string GetAppInstallerPropertyValue(string propertyName) return result; } - /// - /// Installs AppInstaller froma GitHub release. - /// If allUsers is true uses Add-AppxProvisionedPackage otherwise Add-AppxPacakge. - /// - /// Version tag of GitHub release. - /// If install for all users is needed. - public void Install(string versionTag, bool allUsers) - { - if (allUsers) - { - this.AddProvisionPackage(versionTag); - } - else - { - this.AddAppInstallerBundle(versionTag, false); - } - } - - /// - /// Downgrades app installer. - /// - /// Version tag of GitHub release. - /// If install for all users is needed. - public void InstallDowngrade(string versionTag, bool allUsers) - { - this.AddAppInstallerBundle(versionTag, true); - - if (allUsers) - { - // TODO: what happen in add-provisioned with downgrade? - } - } - /// /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml. /// @@ -156,7 +123,43 @@ public void RegisterAppInstaller() }); } - private void AddProvisionPackage(string versionTag) + /// + /// Install AppInstaller bundle from GitHub release. + /// + /// Version tag of GitHub release. + /// If install for all users is needed. + /// Is downgrade. + public void InstallFromGitHubRelease(string versionTag, bool allUsers, bool isDowngrade) + { + using var bundle = new TempFile(); + var gitHubRelease = new GitHubRelease(); + gitHubRelease.DownloadRelease(versionTag, bundle.FullPath); + + this.VerifyDependencies(); + + if (isDowngrade) + { + this.AddAppInstallerBundle(bundle.FullPath, true); + + if (allUsers) + { + this.AddProvisionPackage(bundle.FullPath, versionTag); + } + } + else + { + if (allUsers) + { + this.AddProvisionPackage(bundle.FullPath, versionTag); + } + else + { + this.AddAppInstallerBundle(bundle.FullPath, false); + } + } + } + + private void AddProvisionPackage(string bundlePath, string versionTag) { // TODO: verify system. if (!Utilities.ExecutingAsAdministrator) @@ -165,41 +168,29 @@ private void AddProvisionPackage(string versionTag) throw new System.Exception("Admin bro"); } - using var bundle = new TempFile(); using var licenseFile = new TempFile(); - var gitHubRelease = new GitHubRelease(); - gitHubRelease.DownloadRelease(versionTag, bundle.FullPath); gitHubRelease.DownloadLicense(versionTag, licenseFile.FullPath); - this.VerifyDependencies(); - try { var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); ps.AddCommand("Add-AppxProvisionedPackage") .AddParameter("Online") - .AddParameter("PackagePath", bundle.FullPath) + .AddParameter("PackagePath", bundlePath) .AddParameter("LicensePath", licenseFile.FullPath) .AddParameter(ErrorAction, Stop) .Invoke(); } catch (RuntimeException e) { - this.psCmdlet.WriteError(e.ErrorRecord); + this.psCmdlet.WriteDebug($"Failed installing bundle via Add-AppxProvisionedPackage {e}"); throw e; } } - private void AddAppInstallerBundle(string versionTag, bool downgrade = false) + private void AddAppInstallerBundle(string bundlePath, bool downgrade) { - using var bundle = new TempFile(); - - var gitHubRelease = new GitHubRelease(); - gitHubRelease.DownloadRelease(versionTag, bundle.FullPath); - - this.VerifyDependencies(); - var options = new List(); if (downgrade) { @@ -212,14 +203,14 @@ private void AddAppInstallerBundle(string versionTag, bool downgrade = false) AddAppxPackage, new Dictionary { - { Path, bundle.FullPath }, + { Path, bundlePath }, { ErrorAction, Stop }, }, options); } catch (RuntimeException e) { - this.psCmdlet.WriteError(e.ErrorRecord); + this.psCmdlet.WriteDebug($"Failed installing bundle via Add-AppxPackage {e}"); throw e; } } From 826b2e6cb18e5570a22994e08663b4d74922f560 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Mon, 10 Jul 2023 14:07:23 -0700 Subject: [PATCH 6/9] Xaml release github --- .../Commands/WinGetPackageManagerCommand.cs | 13 +- .../Common/Constants.cs | 27 +++ .../Helpers/AppxModuleHelper.cs | 183 +++++++++--------- .../{GitHubRelease.cs => GitHubClient.cs} | 80 +++----- 4 files changed, 148 insertions(+), 155 deletions(-) rename src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/{GitHubRelease.cs => GitHubClient.cs} (52%) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 19e4aa7918..d55f0b6261 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -14,6 +14,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; using Microsoft.WinGet.Client.Engine.Properties; + using static Microsoft.WinGet.Client.Engine.Common.Constants; /// /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. @@ -39,8 +40,8 @@ public WinGetPackageManagerCommand(PSCmdlet psCmdlet) /// Use prerelease version on GitHub. public void AssertUsingLatest(bool preRelease) { - var gitHubRelease = new GitHubRelease(); - string expectedVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + string expectedVersion = gitHubClient.GetLatestVersionTagName(preRelease); this.Assert(expectedVersion); } @@ -60,8 +61,8 @@ public void Assert(string expectedVersion) /// Install for all users. Requires admin. public void RepairUsingLatest(bool preRelease, bool allUsers) { - var gitHubRelease = new GitHubRelease(); - string expectedVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + string expectedVersion = gitHubClient.GetLatestVersionTagName(preRelease); this.Repair(expectedVersion, allUsers); } @@ -151,8 +152,8 @@ private void Install(string toInstallVersion, bool allUsers) // this particular case we need to. if (string.IsNullOrEmpty(toInstallVersion)) { - var gitHubRelease = new GitHubRelease(); - toInstallVersion = gitHubRelease.GetLatestVersionTagName(false); + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + toInstallVersion = gitHubClient.GetLatestVersionTagName(false); } var appxModule = new AppxModuleHelper(this.PsCmdlet); diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs index c1a14153cd..f117bfeece 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs @@ -33,5 +33,32 @@ internal static class Constants /// Name of PATH environment variable. /// public const string PathEnvVar = "PATH"; + + /// + /// Repository owners. + /// + public class RepositoryOwner + { + /// + /// Microsoft org. + /// + public const string Microsoft = "microsoft"; + } + + /// + /// Repository names. + /// + public class RepositoryName + { + /// + /// https://github.com/microsoft/winget-cli . + /// + public const string WinGetCli = "winget-cli"; + + /// + /// https://github.com/microsoft/microsoft-ui-xaml . + /// + public const string UiXaml = "microsoft-ui-xaml"; + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index fb88da8781..dcc5074a2b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -8,12 +8,11 @@ namespace Microsoft.WinGet.Client.Engine.Helpers { using System.Collections.Generic; using System.Collections.ObjectModel; - using System.IO.Compression; using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Properties; + using static Microsoft.WinGet.Client.Engine.Common.Constants; /// /// Helper to make calls to the Appx module. @@ -46,7 +45,12 @@ internal class AppxModuleHelper private const string AppxManifest = "AppxManifest.xml"; private const string PackageFullName = "PackageFullName"; + // Assets + private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; + private const string License = "License1.xml"; + // Dependencies + // VCLibs private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; private const string VCLibsUWPDesktopVersion = "14.0.30704.0"; private const string VCLibsUWPDesktopX64 = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"; @@ -54,9 +58,13 @@ 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 UiXaml27 = "Microsoft.UI.Xaml.2.7"; - private const string UiXamlNuget = "https://globalcdn.nuget.org/packages/microsoft.ui.xaml.2.7.3.nupkg"; - private const string UiXamlNugetAppxPathFormat = @"{0}\tools\AppX\{1}\Release\Microsoft.UI.Xaml.2.7.appx"; + // Xaml + private const string XamlPackage27 = "Microsoft.UI.Xaml.2.7"; + private const string XamlReleaseTag273 = "v2.7.3"; + private const string XamlAssetX64 = "Microsoft.UI.Xaml.2.7.x64.appx"; + private const string XamlAssetX86 = "Microsoft.UI.Xaml.2.7.x86.appx"; + private const string XamlAssetArm = "Microsoft.UI.Xaml.2.7.arm.appx"; + private const string XamlAssetArm64 = "Microsoft.UI.Xaml.2.7.arm64.appx"; private readonly PSCmdlet psCmdlet; @@ -126,40 +134,36 @@ public void RegisterAppInstaller() /// /// Install AppInstaller bundle from GitHub release. /// - /// Version tag of GitHub release. + /// Release tag of GitHub release. /// If install for all users is needed. /// Is downgrade. - public void InstallFromGitHubRelease(string versionTag, bool allUsers, bool isDowngrade) + public void InstallFromGitHubRelease(string releaseTag, bool allUsers, bool isDowngrade) { - using var bundle = new TempFile(); - var gitHubRelease = new GitHubRelease(); - gitHubRelease.DownloadRelease(versionTag, bundle.FullPath); - this.VerifyDependencies(); if (isDowngrade) { - this.AddAppInstallerBundle(bundle.FullPath, true); + this.AddAppInstallerBundle(releaseTag, true); if (allUsers) { - this.AddProvisionPackage(bundle.FullPath, versionTag); + this.AddProvisionPackage(releaseTag); } } else { if (allUsers) { - this.AddProvisionPackage(bundle.FullPath, versionTag); + this.AddProvisionPackage(releaseTag); } else { - this.AddAppInstallerBundle(bundle.FullPath, false); + this.AddAppInstallerBundle(releaseTag, false); } } } - private void AddProvisionPackage(string bundlePath, string versionTag) + private void AddProvisionPackage(string releaseTag) { // TODO: verify system. if (!Utilities.ExecutingAsAdministrator) @@ -168,16 +172,23 @@ private void AddProvisionPackage(string bundlePath, string versionTag) throw new System.Exception("Admin bro"); } + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + var release = githubClient.GetRelease(releaseTag); + + using var bundleFile = new TempFile(); + var bundleAsset = release.Assets.Where(a => a.Name == MsixBundleName).First(); + githubClient.DownloadUrl(bundleAsset.Url, bundleFile.FullPath); + using var licenseFile = new TempFile(); - var gitHubRelease = new GitHubRelease(); - gitHubRelease.DownloadLicense(versionTag, licenseFile.FullPath); + var licenseAsset = release.Assets.Where(a => a.Name.EndsWith(License)).First(); + githubClient.DownloadUrl(licenseAsset.Url, licenseFile.FullPath); try { var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); ps.AddCommand("Add-AppxProvisionedPackage") .AddParameter("Online") - .AddParameter("PackagePath", bundlePath) + .AddParameter("PackagePath", bundleFile.FullPath) .AddParameter("LicensePath", licenseFile.FullPath) .AddParameter(ErrorAction, Stop) .Invoke(); @@ -189,7 +200,7 @@ private void AddProvisionPackage(string bundlePath, string versionTag) } } - private void AddAppInstallerBundle(string bundlePath, bool downgrade) + private void AddAppInstallerBundle(string releaseTag, bool downgrade) { var options = new List(); if (downgrade) @@ -199,14 +210,12 @@ private void AddAppInstallerBundle(string bundlePath, bool downgrade) try { - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, bundlePath }, - { ErrorAction, Stop }, - }, - options); + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + var release = githubClient.GetRelease(releaseTag); + + using var bundleFile = new TempFile(); + var bundleAsset = release.Assets.Where(a => a.Name == MsixBundleName).First(); + this.AddAppxPackageAsUri(bundleAsset.BrowserDownloadUrl, options); } catch (RuntimeException e) { @@ -215,18 +224,6 @@ private void AddAppInstallerBundle(string bundlePath, bool downgrade) } } - private void VerifyDependencies() - { - // 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. - this.InstallVCLibsDependencies(); - this.InstallUiXaml(); - } - private PSObject GetAppxObject(string packageName) { return this.ExecuteAppxCmdlet( @@ -238,10 +235,20 @@ private PSObject GetAppxObject(string packageName) .FirstOrDefault(); } - private IReadOnlyList GetVCLibsDependencies() + private void VerifyDependencies() { - var vcLibsDependencies = new List(); + // 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. + this.InstallVCLibsDependencies(); + this.InstallUiXaml(); + } + private void InstallVCLibsDependencies() + { var result = this.ExecuteAppxCmdlet( GetAppxPackage, new Dictionary @@ -267,6 +274,8 @@ private IReadOnlyList GetVCLibsDependencies() if (!isInstalled) { this.psCmdlet.WriteDebug("Couldn't find required VCLibs package"); + + var vcLibsDependencies = new List(); var arch = RuntimeInformation.OSArchitecture; if (arch == Architecture.X64) { @@ -288,54 +297,44 @@ private IReadOnlyList GetVCLibsDependencies() { throw new PSNotSupportedException(arch.ToString()); } + + foreach (var vclib in vcLibsDependencies) + { + this.AddAppxPackageAsUri(vclib); + } } else { this.psCmdlet.WriteDebug($"VCLibs are updated."); } - - return vcLibsDependencies; - } - - private void InstallVCLibsDependencies() - { - var packages = this.GetVCLibsDependencies(); - foreach (var package in packages) - { - this.AddAppxPackageAsUri(package); - } } private void InstallUiXaml() { - // TODO: We need to follow up for Microsoft.UI.Xaml.2.7 - // downloading the nuget and extracting it doesn't sound like the right thing to do. - var uiXamlObjs = this.GetAppxObject(UiXaml27); + var uiXamlObjs = this.GetAppxObject(XamlPackage27); if (uiXamlObjs is null) { - // Download xaml nuget, extract and install. - this.psCmdlet.WriteDebug("Downloading and installing Microsoft.UI.Xaml.2.7"); - using var tempFile = new TempFile(); - var githubRelease = new GitHubRelease(); - githubRelease.DownloadUrl(UiXamlNuget, tempFile.FullPath); + var githubRelease = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.UiXaml); - using var tempDir = new TempDirectory(); - ZipFile.ExtractToDirectory(tempFile.FullPath, tempDir.FullDirectoryPath); + var xamlRelease = githubRelease.GetRelease(XamlReleaseTag273); var packagesToInstall = new List(); - var arch = RuntimeInformation.OSArchitecture; - if (arch == Architecture.X64 || - arch == Architecture.X86) + if (arch == Architecture.X64) { - packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, arch)); + packagesToInstall.Add(xamlRelease.Assets.Where(a => a.Name == XamlAssetX64).First().BrowserDownloadUrl); + } + else if (arch == Architecture.X86) + { + packagesToInstall.Add(xamlRelease.Assets.Where(a => a.Name == XamlAssetX86).First().BrowserDownloadUrl); } else if (arch == Architecture.Arm64) { - packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, Architecture.X64)); - packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, Architecture.X86)); - packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, Architecture.Arm)); - packagesToInstall.Add(string.Format(UiXamlNugetAppxPathFormat, tempDir.FullDirectoryPath, arch)); + // Deployment please figure out for me. + packagesToInstall.Add(xamlRelease.Assets.Where(a => a.Name == XamlAssetX64).First().BrowserDownloadUrl); + packagesToInstall.Add(xamlRelease.Assets.Where(a => a.Name == XamlAssetX86).First().BrowserDownloadUrl); + packagesToInstall.Add(xamlRelease.Assets.Where(a => a.Name == XamlAssetArm).First().BrowserDownloadUrl); + packagesToInstall.Add(xamlRelease.Assets.Where(a => a.Name == XamlAssetArm64).First().BrowserDownloadUrl); } else { @@ -344,28 +343,23 @@ private void InstallUiXaml() foreach (var package in packagesToInstall) { - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, package }, - { ErrorAction, Stop }, - }); + this.AddAppxPackageAsUri(package); } } } - private void AddAppxPackageAsUri(string packageUri) + private void AddAppxPackageAsUri(string packageUri, IList options = null) { try { _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, packageUri }, - { ErrorAction, Stop }, - }); + AddAppxPackage, + new Dictionary + { + { Path, packageUri }, + { ErrorAction, Stop }, + }, + options); } catch (RuntimeException e) { @@ -373,7 +367,7 @@ private void AddAppxPackageAsUri(string packageUri) if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.OpenError) { this.psCmdlet.WriteDebug($"Failed adding package [{packageUri}]. Retrying downloading it."); - this.DownloadPackageAndAdd(packageUri); + this.DownloadPackageAndAdd(packageUri, options); } else { @@ -383,21 +377,22 @@ private void AddAppxPackageAsUri(string packageUri) } } - private void DownloadPackageAndAdd(string packageUrl) + private void DownloadPackageAndAdd(string packageUrl, IList options) { using var tempFile = new TempFile(); // This is weird but easy. - var githubRelease = new GitHubRelease(); + var githubRelease = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); githubRelease.DownloadUrl(packageUrl, tempFile.FullPath); _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, tempFile.FullPath }, - { ErrorAction, Stop }, - }); + AddAppxPackage, + new Dictionary + { + { Path, tempFile.FullPath }, + { ErrorAction, Stop }, + }, + options); } private Collection ExecuteAppxCmdlet(string cmdlet, Dictionary parameters = null, IList options = null) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs similarity index 52% rename from src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs rename to src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs index a5abea9939..eafeff8cc3 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------------- -// +// // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // // ----------------------------------------------------------------------------- @@ -9,51 +9,53 @@ namespace Microsoft.WinGet.Client.Engine.Helpers using System; using System.Collections.Generic; using System.IO; - using System.Linq; using System.Threading.Tasks; using Octokit; using FileMode = System.IO.FileMode; /// - /// Handles WinGet's releases in GitHub. + /// Handles GitHub interactions. /// - internal class GitHubRelease + internal class GitHubClient { - private const string Owner = "microsoft"; - private const string Repo = "winget-cli"; private const string UserAgent = "winget-powershell"; - private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; private const string ContentType = "application/octet-stream"; - private const string License = "License1.xml"; + + private readonly string owner; + private readonly string repo; private readonly IGitHubClient gitHubClient; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public GitHubRelease() + /// Owner. + /// Repository. + public GitHubClient(string owner, string repo) { - this.gitHubClient = new GitHubClient(new ProductHeaderValue(UserAgent)); + this.gitHubClient = new Octokit.GitHubClient(new ProductHeaderValue(UserAgent)); + this.owner = owner; + this.repo = repo; } /// - /// Download a release msixbundle from winget-cli. + /// Get a release. /// - /// Optional release name. If null, gets latest. - /// Output file. - public void DownloadRelease(string releaseTag, string outputFile) + /// Release tag. + /// The Release. + public Release GetRelease(string releaseTag) { - this.DownloadReleaseAsync(releaseTag, outputFile).GetAwaiter().GetResult(); + return this.GetReleaseAsync(releaseTag).GetAwaiter().GetResult(); } /// - /// Download a release license file from winget-cli. + /// Get a release. /// - /// Optional release name. If null, gets latest. - /// Output file. - public void DownloadLicense(string releaseTag, string outputFile) + /// Release tag. + /// The Release. + public async Task GetReleaseAsync(string releaseTag) { - this.DownloadLicenseAsync(releaseTag, outputFile).GetAwaiter().GetResult(); + return await this.gitHubClient.Repository.Release.Get(this.owner, this.repo, releaseTag); } /// @@ -76,38 +78,6 @@ public void DownloadUrl(string url, string fileName) this.DownloadUrlAsync(url, fileName).GetAwaiter().GetResult(); } - /// - /// Download asynchronously a release from winget-cli. - /// - /// Optional release name. If null, gets latest. - /// Output file. - /// A representing the asynchronous operation. - public async Task DownloadReleaseAsync(string releaseTag, string outputFile) - { - Release release = await this.gitHubClient.Repository.Release.Get(Owner, Repo, releaseTag); - - // Get asset and download. - var msixBundleAsset = release.Assets.Where(a => a.Name == MsixBundleName).First(); - - await this.DownloadUrlAsync(msixBundleAsset.Url, outputFile); - } - - /// - /// Downloads the license xml file from the release. - /// - /// Release tag. - /// Output file. - /// A representing the asynchronous operation. - public async Task DownloadLicenseAsync(string releaseTag, string outputFile) - { - Release release = await this.gitHubClient.Repository.Release.Get(Owner, Repo, releaseTag); - - // Get asset and download. - var licenseAsset = release.Assets.Where(a => a.Name.EndsWith(License)).First(); - - await this.DownloadUrlAsync(licenseAsset.Url, outputFile); - } - /// /// Downloads a file from a url. /// @@ -140,11 +110,11 @@ internal async Task GetLatestVersionAsync(bool includePreRelease) if (includePreRelease) { // GetAll orders by newest and includes pre releases. - release = (await this.gitHubClient.Repository.Release.GetAll(Owner, Repo))[0]; + release = (await this.gitHubClient.Repository.Release.GetAll(this.owner, this.repo))[0]; } else { - release = await this.gitHubClient.Repository.Release.GetLatest(Owner, Repo); + release = await this.gitHubClient.Repository.Release.GetLatest(this.owner, this.repo); } return release; From 8aa191b9f18abf3e78f1391e1088da44ef7879bb Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Mon, 10 Jul 2023 15:23:26 -0700 Subject: [PATCH 7/9] Exception messages --- .../Commands/WinGetPackageManagerCommand.cs | 31 +++++--- .../Common/WinGetIntegrity.cs | 2 +- .../Exceptions/WinGetRepairException.cs | 70 +++++++++++++++++++ .../Helpers/AppxModuleHelper.cs | 30 ++++---- .../Helpers/GitHubClient.cs | 4 +- .../Properties/Resources.Designer.cs | 45 +++++++++--- .../Properties/Resources.resx | 13 +++- 7 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairException.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index d55f0b6261..5e44c8df53 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -23,8 +23,6 @@ public sealed class WinGetPackageManagerCommand : BaseCommand { private const string EnvPath = "env:PATH"; - private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; - /// /// Initializes a new instance of the class. /// @@ -73,6 +71,19 @@ public void RepairUsingLatest(bool preRelease, bool allUsers) /// Install for all users. Requires admin. public void Repair(string expectedVersion, bool allUsers) { + if (allUsers) + { + if (Utilities.ExecutingAsSystem) + { + throw new NotSupportedException(); + } + + if (!Utilities.ExecutingAsAdministrator) + { + throw new WinGetRepairException(Resources.RepairAllUsersMessage); + } + } + this.RepairStateMachine(expectedVersion, allUsers, new HashSet()); } @@ -111,20 +122,20 @@ private void RepairStateMachine(string expectedVersion, bool allUsers, HashSet +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Properties; + + /// + /// WinGet repair exception. + /// + [Serializable] + public class WinGetRepairException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// Integrity exception. + public WinGetRepairException(WinGetIntegrityException ie) + : base(GetMessage(ie), ie) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Inner exception. + public WinGetRepairException(Exception e) + : base(Resources.RepairFailureMessage, e) + { + } + + /// + /// Initializes a new instance of the class. + /// + public WinGetRepairException() + : base(Resources.RepairFailureMessage) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Message.. + public WinGetRepairException(string message) + : base(message) + { + } + + private static string GetMessage(WinGetIntegrityException ie) + { + string message = Resources.RepairFailureMessage; + if (ie.Category == IntegrityCategory.AppInstallerNoLicense) + { + message += $" {Resources.RepairAllUsersHelpMessage}"; + } + else if (ie.Category == IntegrityCategory.AppExecutionAliasDisabled) + { + message += $" {Resources.RepairAppExecutionAliasMessage}"; + } + + return message; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index dcc5074a2b..ecd1c68179 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -23,17 +23,21 @@ internal class AppxModuleHelper private const string ImportModule = "Import-Module"; private const string GetAppxPackage = "Get-AppxPackage"; private const string AddAppxPackage = "Add-AppxPackage"; + private const string AddAppxProvisionedPackage = "Add-AppxProvisionedPackage"; // Parameters name private const string Name = "Name"; private const string Path = "Path"; private const string ErrorAction = "ErrorAction"; private const string WarningAction = "WarningAction"; + private const string PackagePath = "PackagePath"; + private const string LicensePath = "LicensePath"; // Parameter Values private const string Appx = "Appx"; private const string Stop = "Stop"; private const string SilentlyContinue = "SilentlyContinue"; + private const string Online = "Online"; // Options private const string UseWindowsPowerShell = "UseWindowsPowerShell"; @@ -132,17 +136,18 @@ public void RegisterAppInstaller() } /// - /// Install AppInstaller bundle from GitHub release. + /// Install AppInstaller's bundle from a GitHub release. /// /// Release tag of GitHub release. /// If install for all users is needed. /// Is downgrade. public void InstallFromGitHubRelease(string releaseTag, bool allUsers, bool isDowngrade) { - this.VerifyDependencies(); + this.InstallDependencies(); if (isDowngrade) { + // Add-AppxProvisionedPackage doesn't support downgrade. this.AddAppInstallerBundle(releaseTag, true); if (allUsers) @@ -165,31 +170,24 @@ public void InstallFromGitHubRelease(string releaseTag, bool allUsers, bool isDo private void AddProvisionPackage(string releaseTag) { - // TODO: verify system. - if (!Utilities.ExecutingAsAdministrator) - { - // Add-AppxProvisionedPackage - throw new System.Exception("Admin bro"); - } - var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); var release = githubClient.GetRelease(releaseTag); using var bundleFile = new TempFile(); var bundleAsset = release.Assets.Where(a => a.Name == MsixBundleName).First(); - githubClient.DownloadUrl(bundleAsset.Url, bundleFile.FullPath); + githubClient.DownloadUrl(bundleAsset.BrowserDownloadUrl, bundleFile.FullPath); using var licenseFile = new TempFile(); var licenseAsset = release.Assets.Where(a => a.Name.EndsWith(License)).First(); - githubClient.DownloadUrl(licenseAsset.Url, licenseFile.FullPath); + githubClient.DownloadUrl(licenseAsset.BrowserDownloadUrl, licenseFile.FullPath); try { var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - ps.AddCommand("Add-AppxProvisionedPackage") - .AddParameter("Online") - .AddParameter("PackagePath", bundleFile.FullPath) - .AddParameter("LicensePath", licenseFile.FullPath) + ps.AddCommand(AddAppxProvisionedPackage) + .AddParameter(Online) + .AddParameter(PackagePath, bundleFile.FullPath) + .AddParameter(LicensePath, licenseFile.FullPath) .AddParameter(ErrorAction, Stop) .Invoke(); } @@ -235,7 +233,7 @@ private PSObject GetAppxObject(string packageName) .FirstOrDefault(); } - private void VerifyDependencies() + private void InstallDependencies() { // A better implementation would use Add-AppxPackage with -DependencyPath, but // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs index eafeff8cc3..56b295a814 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs @@ -39,7 +39,7 @@ public GitHubClient(string owner, string repo) } /// - /// Get a release. + /// Gets a release. /// /// Release tag. /// The Release. @@ -49,7 +49,7 @@ public Release GetRelease(string releaseTag) } /// - /// Get a release. + /// Gets a release. /// /// Release tag. /// The Release. diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs index baa0d412e4..ecf63b656e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -60,15 +60,6 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it.. - /// - internal static string AppExecutionAliasDisabledHelpMessage { - get { - return ResourceManager.GetString("AppExecutionAliasDisabledHelpMessage", resourceCulture); - } - } - /// /// Looks up a localized string similar to An error occurred while connecting to the catalog.. /// @@ -240,6 +231,42 @@ internal static string ProgressRecordActivityUpdating { } } + /// + /// Looks up a localized string similar to Try running with -AllUsers in administrator mode.. + /// + internal static string RepairAllUsersHelpMessage { + get { + return ResourceManager.GetString("RepairAllUsersHelpMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to -AllUsers requires administrator mode.. + /// + internal static string RepairAllUsersMessage { + get { + return ResourceManager.GetString("RepairAllUsersMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it.. + /// + internal static string RepairAppExecutionAliasMessage { + get { + return ResourceManager.GetString("RepairAppExecutionAliasMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to repair winget.. + /// + internal static string RepairFailureMessage { + get { + return ResourceManager.GetString("RepairFailureMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA).. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx index 5414cfeb99..5e5cc9ad4c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -186,7 +186,7 @@ The App Installer is not registered. - + The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it. @@ -210,4 +210,15 @@ No applicable license found. + + Try running with -AllUsers in administrator mode. + {Locked="-AllUsers"} + + + -AllUsers requires administrator mode. + {Locked="-AllUsers"} + + + Failed to repair winget. + \ No newline at end of file From 658690594683da3102e6f15740342d0e4b89def9 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Mon, 10 Jul 2023 15:41:21 -0700 Subject: [PATCH 8/9] expect --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 82125161ad..ee529de18c 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -457,6 +457,7 @@ UWP VALUENAMECASE VERSI VERSIE +vclib vns vsconfig vstest From 121c3cc1a9566adf2f4843d28e38555524226d0c Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Thu, 13 Jul 2023 11:40:56 -0700 Subject: [PATCH 9/9] no recursion --- .../Commands/WinGetPackageManagerCommand.cs | 101 ++++++++++-------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 5e44c8df53..a6d534449d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -84,63 +84,70 @@ public void Repair(string expectedVersion, bool allUsers) } } - this.RepairStateMachine(expectedVersion, allUsers, new HashSet()); + this.RepairStateMachine(expectedVersion, allUsers); } - private void RepairStateMachine(string expectedVersion, bool allUsers, HashSet seenCategories) + private void RepairStateMachine(string expectedVersion, bool allUsers) { - try - { - WinGetIntegrity.AssertWinGet(this.PsCmdlet, expectedVersion); - this.PsCmdlet.WriteDebug($"WinGet is in a good state."); - } - catch (WinGetIntegrityException e) + var seenCategories = new HashSet(); + + var currentCategory = IntegrityCategory.Unknown; + while (currentCategory != IntegrityCategory.Installed) { - if (seenCategories.Contains(e.Category)) + try { - this.PsCmdlet.WriteDebug($"{e.Category} encountered previously"); - throw; + WinGetIntegrity.AssertWinGet(this.PsCmdlet, expectedVersion); + this.PsCmdlet.WriteDebug($"WinGet is in a good state."); + currentCategory = IntegrityCategory.Installed; } - - this.PsCmdlet.WriteDebug($"Integrity category type: {e.Category}"); - seenCategories.Add(e.Category); - - switch (e.Category) + catch (WinGetIntegrityException e) { - case IntegrityCategory.UnexpectedVersion: - this.InstallDifferentVersion(new WinGetVersion(expectedVersion), allUsers); - break; - case IntegrityCategory.NotInPath: - this.RepairEnvPath(); - break; - case IntegrityCategory.AppInstallerNotRegistered: - this.Register(); - break; - case IntegrityCategory.AppInstallerNotInstalled: - case IntegrityCategory.AppInstallerNotSupported: - case IntegrityCategory.Failure: - this.Install(expectedVersion, allUsers); - break; - case IntegrityCategory.AppInstallerNoLicense: - // This requires -AllUsers in admin mode. - if (allUsers && Utilities.ExecutingAsAdministrator) - { + currentCategory = e.Category; + + if (seenCategories.Contains(currentCategory)) + { + this.PsCmdlet.WriteDebug($"{currentCategory} encountered previously"); + throw; + } + + this.PsCmdlet.WriteDebug($"Integrity category type: {currentCategory}"); + seenCategories.Add(currentCategory); + + switch (currentCategory) + { + case IntegrityCategory.UnexpectedVersion: + this.InstallDifferentVersion(new WinGetVersion(expectedVersion), allUsers); + break; + case IntegrityCategory.NotInPath: + this.RepairEnvPath(); + break; + case IntegrityCategory.AppInstallerNotRegistered: + this.Register(); + break; + case IntegrityCategory.AppInstallerNotInstalled: + case IntegrityCategory.AppInstallerNotSupported: + case IntegrityCategory.Failure: this.Install(expectedVersion, allUsers); - } - else - { + break; + case IntegrityCategory.AppInstallerNoLicense: + // This requires -AllUsers in admin mode. + if (allUsers && Utilities.ExecutingAsAdministrator) + { + this.Install(expectedVersion, allUsers); + } + else + { + throw new WinGetRepairException(e); + } + + break; + case IntegrityCategory.AppExecutionAliasDisabled: + case IntegrityCategory.Unknown: throw new WinGetRepairException(e); - } - - break; - case IntegrityCategory.AppExecutionAliasDisabled: - case IntegrityCategory.Unknown: - throw new WinGetRepairException(e); - default: - throw new NotSupportedException(); + default: + throw new NotSupportedException(); + } } - - this.RepairStateMachine(expectedVersion, allUsers, seenCategories); } }