winget-cli

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

WinGetPackageManagerCommand.cs (10543B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="WinGetPackageManagerCommand.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace Microsoft.WinGet.Client.Engine.Commands
      8 {
      9     using System;
     10     using System.Collections.Generic;
     11     using System.Management.Automation;
     12     using System.Threading.Tasks;
     13     using Microsoft.WinGet.Client.Engine.Commands.Common;
     14     using Microsoft.WinGet.Client.Engine.Common;
     15     using Microsoft.WinGet.Client.Engine.Exceptions;
     16     using Microsoft.WinGet.Client.Engine.Helpers;
     17     using Microsoft.WinGet.Common.Command;
     18     using Microsoft.WinGet.Resources;
     19     using static Microsoft.WinGet.Client.Engine.Common.Constants;
     20 
     21     /// <summary>
     22     /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager.
     23     /// </summary>
     24     public sealed class WinGetPackageManagerCommand : BaseCommand
     25     {
     26         private const string EnvPath = "env:PATH";
     27 
     28         /// <summary>
     29         /// Initializes a new instance of the <see cref="WinGetPackageManagerCommand"/> class.
     30         /// </summary>
     31         /// <param name="psCmdlet">Cmdlet being executed.</param>
     32         public WinGetPackageManagerCommand(PSCmdlet psCmdlet)
     33             : base(psCmdlet)
     34         {
     35         }
     36 
     37         /// <summary>
     38         /// Asserts winget version is the latest version on winget-cli.
     39         /// </summary>
     40         /// <param name="preRelease">Use prerelease version on GitHub.</param>
     41         public void AssertUsingLatest(bool preRelease)
     42         {
     43             var runningTask = this.RunOnMTA(
     44                 async () =>
     45                 {
     46                     var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
     47                     string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease);
     48                     this.Assert(expectedVersion);
     49                     return true;
     50                 });
     51 
     52             this.Wait(runningTask);
     53         }
     54 
     55         /// <summary>
     56         /// Asserts the version installed is the specified.
     57         /// </summary>
     58         /// <param name="expectedVersion">The expected version.</param>
     59         public void Assert(string expectedVersion)
     60         {
     61             WinGetIntegrity.AssertWinGet(this, expectedVersion);
     62         }
     63 
     64         /// <summary>
     65         /// Repairs winget using the latest version on winget-cli.
     66         /// </summary>
     67         /// <param name="preRelease">Use prerelease version on GitHub.</param>
     68         /// <param name="allUsers">Install for all users. Requires admin.</param>
     69         /// <param name="force">Force application shutdown.</param>
     70         public void RepairUsingLatest(bool preRelease, bool allUsers, bool force)
     71         {
     72             this.ValidateWhenAllUsers(allUsers);
     73             var runningTask = this.RunOnMTA(
     74                 async () =>
     75                 {
     76                     var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
     77                     string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease);
     78                     await this.RepairStateMachineAsync(expectedVersion, allUsers, force);
     79                     return true;
     80                 });
     81 
     82             this.Wait(runningTask);
     83         }
     84 
     85         /// <summary>
     86         /// Repairs winget if needed.
     87         /// </summary>
     88         /// <param name="expectedVersion">The expected version, if any.</param>
     89         /// <param name="allUsers">Install for all users. Requires admin.</param>
     90         /// <param name="force">Force application shutdown.</param>
     91         public void Repair(string expectedVersion, bool allUsers, bool force)
     92         {
     93             this.ValidateWhenAllUsers(allUsers);
     94             var runningTask = this.RunOnMTA(
     95                 async () =>
     96                 {
     97                     await this.RepairStateMachineAsync(expectedVersion, allUsers, force);
     98                     return true;
     99                 });
    100             this.Wait(runningTask);
    101         }
    102 
    103         private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force)
    104         {
    105             var seenCategories = new HashSet<IntegrityCategory>();
    106             var cancellationToken = this.GetCancellationToken();
    107 
    108             var currentCategory = IntegrityCategory.Unknown;
    109             while (currentCategory != IntegrityCategory.Installed)
    110             {
    111                 cancellationToken.ThrowIfCancellationRequested();
    112 
    113                 try
    114                 {
    115                     WinGetIntegrity.AssertWinGet(this, expectedVersion);
    116                     this.Write(StreamType.Verbose, $"WinGet is in a good state.");
    117                     currentCategory = IntegrityCategory.Installed;
    118                 }
    119                 catch (WinGetIntegrityException e)
    120                 {
    121                     currentCategory = e.Category;
    122 
    123                     if (seenCategories.Contains(currentCategory))
    124                     {
    125                         this.Write(StreamType.Verbose, $"{currentCategory} encountered previously");
    126                         throw;
    127                     }
    128 
    129                     this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}");
    130                     seenCategories.Add(currentCategory);
    131 
    132                     switch (currentCategory)
    133                     {
    134                         case IntegrityCategory.UnexpectedVersion:
    135                             await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), allUsers, force);
    136                             break;
    137                         case IntegrityCategory.NotInPath:
    138                             this.RepairEnvPath();
    139                             break;
    140                         case IntegrityCategory.AppInstallerNotRegistered:
    141                             this.Register(expectedVersion);
    142                             break;
    143                         case IntegrityCategory.AppInstallerNotInstalled:
    144                         case IntegrityCategory.AppInstallerNotSupported:
    145                         case IntegrityCategory.Failure:
    146                             await this.InstallAsync(expectedVersion, allUsers, force);
    147                             break;
    148                         case IntegrityCategory.AppInstallerNoLicense:
    149                             // This requires -AllUsers in admin mode.
    150                             if (allUsers && Utilities.ExecutingAsAdministrator)
    151                             {
    152                                 await this.InstallAsync(expectedVersion, allUsers, force);
    153                             }
    154                             else
    155                             {
    156                                 throw new WinGetRepairException(e);
    157                             }
    158 
    159                             break;
    160                         case IntegrityCategory.AppExecutionAliasDisabled:
    161                         case IntegrityCategory.Unknown:
    162                             throw new WinGetRepairException(e);
    163                         default:
    164                             throw new NotSupportedException();
    165                     }
    166                 }
    167             }
    168         }
    169 
    170         private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force)
    171         {
    172             var installedVersion = WinGetVersion.InstalledWinGetVersion(this);
    173             bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0;
    174 
    175             string message = $"Installed WinGet version '{installedVersion.TagVersion}' " +
    176                 $"Installing WinGet version '{toInstallVersion.TagVersion}' " +
    177                 $"Is downgrade {isDowngrade}";
    178             this.Write(
    179                 StreamType.Verbose,
    180                 message);
    181             var appxModule = new AppxModuleHelper(this);
    182             await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force);
    183         }
    184 
    185         private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force)
    186         {
    187             // If we are here and toInstallVersion is empty, it means that they just ran Repair-WinGetPackageManager.
    188             // When there is not version specified, we don't want to assume an empty version means latest, but in
    189             // this particular case we need to.
    190             if (string.IsNullOrEmpty(toInstallVersion))
    191             {
    192                 var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
    193                 toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false);
    194             }
    195 
    196             var appxModule = new AppxModuleHelper(this);
    197             await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force);
    198         }
    199 
    200         private void Register(string toRegisterVersion)
    201         {
    202             var appxModule = new AppxModuleHelper(this);
    203             appxModule.RegisterAppInstaller(toRegisterVersion);
    204         }
    205 
    206         private void RepairEnvPath()
    207         {
    208             // Add windows app path to user PATH environment variable
    209             Utilities.AddWindowsAppToPath();
    210 
    211             // Update this sessions PowerShell environment so the user doesn't have to restart the terminal.
    212             string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User);
    213             string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine);
    214             string newPwshPathEnv = $"{envPathMachine};{envPathUser}";
    215             this.SetVariable(EnvPath, newPwshPathEnv);
    216 
    217             this.Write(StreamType.Verbose, $"PATH environment variable updated");
    218         }
    219 
    220         private void ValidateWhenAllUsers(bool allUsers)
    221         {
    222             if (allUsers)
    223             {
    224                 if (Utilities.ExecutingAsSystem)
    225                 {
    226                     throw new NotSupportedException();
    227                 }
    228 
    229                 if (!Utilities.ExecutingAsAdministrator)
    230                 {
    231                     throw new WinGetRepairException(Resources.RepairAllUsersMessage);
    232                 }
    233             }
    234         }
    235     }
    236 }