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 }