From 48792874db6a32d2664c7bad2a1e284acab95f28 Mon Sep 17 00:00:00 2001 From: MengAiDev Meng <3463526515@qq.com> Date: Fri, 29 Aug 2025 10:12:06 +0000 Subject: [PATCH 1/2] Fix line endings and add IncludePrerelease parameter support - Convert CRLF to LF line endings across PowerShell cmdlets - Add IncludePrerelease parameter to Assert-WinGetPackageManager and Repair-WinGetPackageManager - Make IncludePrerelease parameter available across all parameter sets - Update WinGetPackageManagerCommand to handle prerelease versions in repair operations --- .../AssertWinGetPackageManagerCmdlet.cs | 82 +-- .../Common/WinGetPackageManagerCmdlet.cs | 81 ++- .../RepairWinGetPackageManagerCmdlet.cs | 134 ++--- .../Commands/WinGetPackageManagerCommand.cs | 484 +++++++++--------- 4 files changed, 396 insertions(+), 385 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs index b33bdbd502..712c464d26 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Assert-WinGetPackageManager. Verifies winget is installed properly. - /// - [Cmdlet( - VerbsLifecycle.Assert, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [Alias("awgpm")] - public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet - { - /// - /// Validates winget is installed correctly. If not, throws an exception - /// with the reason why, if any. - /// - protected override void ProcessRecord() - { - var command = new WinGetPackageManagerCommand(this); - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - command.AssertUsingLatest(this.IncludePrerelease.ToBool()); - } - else - { - command.Assert(this.Version); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Assert-WinGetPackageManager. Verifies winget is installed properly. + /// + [Cmdlet( + VerbsLifecycle.Assert, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [Alias("awgpm")] + public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + /// + /// Validates winget is installed correctly. If not, throws an exception + /// with the reason why, if any. + /// + protected override void ProcessRecord() + { + var command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + command.AssertUsingLatest(this.IncludePrerelease.ToBool()); + } + else + { + command.Assert(this.Version, this.IncludePrerelease.ToBool()); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs index 3d2f4d16d7..2977559cc2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs @@ -1,41 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - - /// - /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. - /// - public abstract class WinGetPackageManagerCmdlet : PSCmdlet - { - /// - /// Gets or sets the optional version. - /// - [Parameter( - ParameterSetName = Constants.IntegrityVersionSet, - ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether to use latest. - /// - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter Latest { get; set; } - - /// - /// Gets or sets a value indicating whether to include prerelease winget versions. - /// - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludePrerelease { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + + /// + /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. + /// + public abstract class WinGetPackageManagerCmdlet : PSCmdlet + { + /// + /// Gets or sets the optional version. + /// + [Parameter( + ParameterSetName = Constants.IntegrityVersionSet, + ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether to use latest. + /// + [Parameter( + ParameterSetName = Constants.IntegrityLatestSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter Latest { get; set; } + + /// + /// Gets or sets a value indicating whether to include prerelease winget versions. + /// + [Parameter( + ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludePrerelease { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs index b3e6ae4df2..c53bac23e0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs @@ -1,67 +1,67 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Repair-WinGetPackageManager. Repairs winget if needed. - /// - [Cmdlet( - VerbsDiagnostic.Repair, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [Alias("rpwgpm")] - [OutputType(typeof(int))] - public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet - { - private WinGetPackageManagerCommand command = null; - - /// - /// Gets or sets a value indicating whether to repair for all users. Requires admin. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AllUsers { get; set; } - - /// - /// Gets or sets a value indicating whether to force application shutdown. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// - /// Attempts to repair winget. - /// TODO: consider WhatIf and Confirm options. - /// - protected override void ProcessRecord() - { - this.command = new WinGetPackageManagerCommand(this); - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - this.command.RepairUsingLatest(this.IncludePrerelease.ToBool(), this.AllUsers.ToBool(), this.Force.ToBool()); - } - else - { - this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool()); - } - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Repair-WinGetPackageManager. Repairs winget if needed. + /// + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [Alias("rpwgpm")] + [OutputType(typeof(int))] + public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + private WinGetPackageManagerCommand command = null; + + /// + /// Gets or sets a value indicating whether to repair for all users. Requires admin. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllUsers { get; set; } + + /// + /// Gets or sets a value indicating whether to force application shutdown. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Attempts to repair winget. + /// TODO: consider WhatIf and Confirm options. + /// + protected override void ProcessRecord() + { + this.command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + this.command.RepairUsingLatest(this.IncludePrerelease.ToBool(), this.AllUsers.ToBool(), this.Force.ToBool()); + } + else + { + this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool(), this.IncludePrerelease.ToBool()); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index cdf5f16d51..ab62170916 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -1,236 +1,248 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Threading.Tasks; - 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.Common.Command; - using Microsoft.WinGet.Resources; - using static Microsoft.WinGet.Client.Engine.Common.Constants; - - /// - /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. - /// - public sealed class WinGetPackageManagerCommand : BaseCommand - { - private const string EnvPath = "env:PATH"; - - /// - /// Initializes a new instance of the class. - /// - /// Cmdlet being executed. - public WinGetPackageManagerCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Asserts winget version is the latest version on winget-cli. - /// - /// Use prerelease version on GitHub. - public void AssertUsingLatest(bool preRelease) - { - var runningTask = this.RunOnMTA( - async () => - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); - this.Assert(expectedVersion); - return true; - }); - - this.Wait(runningTask); - } - - /// - /// Asserts the version installed is the specified. - /// - /// The expected version. - public void Assert(string expectedVersion) - { - WinGetIntegrity.AssertWinGet(this, expectedVersion); - } - - /// - /// Repairs winget using the latest version on winget-cli. - /// - /// Use prerelease version on GitHub. - /// Install for all users. Requires admin. - /// Force application shutdown. - public void RepairUsingLatest(bool preRelease, bool allUsers, bool force) - { - this.ValidateWhenAllUsers(allUsers); - var runningTask = this.RunOnMTA( - async () => - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); - await this.RepairStateMachineAsync(expectedVersion, allUsers, force); - return true; - }); - - this.Wait(runningTask); - } - - /// - /// Repairs winget if needed. - /// - /// The expected version, if any. - /// Install for all users. Requires admin. - /// Force application shutdown. - public void Repair(string expectedVersion, bool allUsers, bool force) - { - this.ValidateWhenAllUsers(allUsers); - var runningTask = this.RunOnMTA( - async () => - { - await this.RepairStateMachineAsync(expectedVersion, allUsers, force); - return true; - }); - this.Wait(runningTask); - } - - private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force) - { - var seenCategories = new HashSet(); - var cancellationToken = this.GetCancellationToken(); - - var currentCategory = IntegrityCategory.Unknown; - while (currentCategory != IntegrityCategory.Installed) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - WinGetIntegrity.AssertWinGet(this, expectedVersion); - this.Write(StreamType.Verbose, $"WinGet is in a good state."); - currentCategory = IntegrityCategory.Installed; - } - catch (WinGetIntegrityException e) - { - currentCategory = e.Category; - - if (seenCategories.Contains(currentCategory)) - { - this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); - throw; - } - - this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}"); - seenCategories.Add(currentCategory); - - switch (currentCategory) - { - case IntegrityCategory.UnexpectedVersion: - await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), allUsers, force); - break; - case IntegrityCategory.NotInPath: - this.RepairEnvPath(); - break; - case IntegrityCategory.AppInstallerNotRegistered: - this.Register(expectedVersion); - break; - case IntegrityCategory.AppInstallerNotInstalled: - case IntegrityCategory.AppInstallerNotSupported: - case IntegrityCategory.Failure: - await this.InstallAsync(expectedVersion, allUsers, force); - break; - case IntegrityCategory.AppInstallerNoLicense: - // This requires -AllUsers in admin mode. - if (allUsers && Utilities.ExecutingAsAdministrator) - { - await this.InstallAsync(expectedVersion, allUsers, force); - } - else - { - throw new WinGetRepairException(e); - } - - break; - case IntegrityCategory.AppExecutionAliasDisabled: - case IntegrityCategory.Unknown: - throw new WinGetRepairException(e); - default: - throw new NotSupportedException(); - } - } - } - } - - private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force) - { - var installedVersion = WinGetVersion.InstalledWinGetVersion(this); - bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; - - string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + - $"Installing WinGet version '{toInstallVersion.TagVersion}' " + - $"Is downgrade {isDowngrade}"; - this.Write( - StreamType.Verbose, - message); - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force); - } - - private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force) - { - // 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(toInstallVersion)) - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false); - } - - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force); - } - - private void Register(string toRegisterVersion) - { - var appxModule = new AppxModuleHelper(this); - appxModule.RegisterAppInstaller(toRegisterVersion); - } - - private void RepairEnvPath() - { - // Add windows app path to user PATH environment variable - Utilities.AddWindowsAppToPath(); - - // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. - string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); - string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); - string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; - this.SetVariable(EnvPath, newPwshPathEnv); - - this.Write(StreamType.Verbose, $"PATH environment variable updated"); - } - - private void ValidateWhenAllUsers(bool allUsers) - { - if (allUsers) - { - if (Utilities.ExecutingAsSystem) - { - throw new NotSupportedException(); - } - - if (!Utilities.ExecutingAsAdministrator) - { - throw new WinGetRepairException(Resources.RepairAllUsersMessage); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading.Tasks; + 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.Common.Command; + using Microsoft.WinGet.Resources; + using static Microsoft.WinGet.Client.Engine.Common.Constants; + + /// + /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. + /// + public sealed class WinGetPackageManagerCommand : BaseCommand + { + private const string EnvPath = "env:PATH"; + + /// + /// Initializes a new instance of the class. + /// + /// Cmdlet being executed. + public WinGetPackageManagerCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Asserts winget version is the latest version on winget-cli. + /// + /// Use prerelease version on GitHub. + public void AssertUsingLatest(bool preRelease) + { + var runningTask = this.RunOnMTA( + async () => + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); + this.Assert(expectedVersion); + return true; + }); + + this.Wait(runningTask); + } + + /// + /// Asserts the version installed is the specified. + /// + /// The expected version. + /// Include prerelease versions when validating the specified version. + public void Assert(string expectedVersion, bool preRelease = false) + { + // The preRelease parameter is for consistency but not used in this method + // since we're checking for an exact version match + WinGetIntegrity.AssertWinGet(this, expectedVersion); + } + + /// + /// Repairs winget using the latest version on winget-cli. + /// + /// Use prerelease version on GitHub. + /// Install for all users. Requires admin. + /// Force application shutdown. + public void RepairUsingLatest(bool preRelease, bool allUsers, bool force) + { + this.ValidateWhenAllUsers(allUsers); + var runningTask = this.RunOnMTA( + async () => + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); + await this.RepairStateMachineAsync(expectedVersion, allUsers, force); + return true; + }); + + this.Wait(runningTask); + } + + /// + /// Repairs winget if needed. + /// + /// The expected version, if any. + /// Install for all users. Requires admin. + /// Force application shutdown. + /// Include prerelease versions when searching for the specified version. + public void Repair(string expectedVersion, bool allUsers, bool force, bool preRelease = false) + { + this.ValidateWhenAllUsers(allUsers); + var runningTask = this.RunOnMTA( + async () => + { + // If no specific version is provided and IncludePrerelease is specified, + // we need to get the latest prerelease version + if (string.IsNullOrEmpty(expectedVersion) && preRelease) + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(true); + } + + await this.RepairStateMachineAsync(expectedVersion, allUsers, force); + return true; + }); + this.Wait(runningTask); + } + + private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force) + { + var seenCategories = new HashSet(); + var cancellationToken = this.GetCancellationToken(); + + var currentCategory = IntegrityCategory.Unknown; + while (currentCategory != IntegrityCategory.Installed) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + WinGetIntegrity.AssertWinGet(this, expectedVersion); + this.Write(StreamType.Verbose, $"WinGet is in a good state."); + currentCategory = IntegrityCategory.Installed; + } + catch (WinGetIntegrityException e) + { + currentCategory = e.Category; + + if (seenCategories.Contains(currentCategory)) + { + this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); + throw; + } + + this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}"); + seenCategories.Add(currentCategory); + + switch (currentCategory) + { + case IntegrityCategory.UnexpectedVersion: + await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), allUsers, force); + break; + case IntegrityCategory.NotInPath: + this.RepairEnvPath(); + break; + case IntegrityCategory.AppInstallerNotRegistered: + this.Register(expectedVersion); + break; + case IntegrityCategory.AppInstallerNotInstalled: + case IntegrityCategory.AppInstallerNotSupported: + case IntegrityCategory.Failure: + await this.InstallAsync(expectedVersion, allUsers, force); + break; + case IntegrityCategory.AppInstallerNoLicense: + // This requires -AllUsers in admin mode. + if (allUsers && Utilities.ExecutingAsAdministrator) + { + await this.InstallAsync(expectedVersion, allUsers, force); + } + else + { + throw new WinGetRepairException(e); + } + + break; + case IntegrityCategory.AppExecutionAliasDisabled: + case IntegrityCategory.Unknown: + throw new WinGetRepairException(e); + default: + throw new NotSupportedException(); + } + } + } + } + + private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force) + { + var installedVersion = WinGetVersion.InstalledWinGetVersion(this); + bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; + + string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + + $"Installing WinGet version '{toInstallVersion.TagVersion}' " + + $"Is downgrade {isDowngrade}"; + this.Write( + StreamType.Verbose, + message); + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force); + } + + private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force) + { + // 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(toInstallVersion)) + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false); + } + + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force); + } + + private void Register(string toRegisterVersion) + { + var appxModule = new AppxModuleHelper(this); + appxModule.RegisterAppInstaller(toRegisterVersion); + } + + private void RepairEnvPath() + { + // Add windows app path to user PATH environment variable + Utilities.AddWindowsAppToPath(); + + // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. + string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); + string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); + string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; + this.SetVariable(EnvPath, newPwshPathEnv); + + this.Write(StreamType.Verbose, $"PATH environment variable updated"); + } + + private void ValidateWhenAllUsers(bool allUsers) + { + if (allUsers) + { + if (Utilities.ExecutingAsSystem) + { + throw new NotSupportedException(); + } + + if (!Utilities.ExecutingAsAdministrator) + { + throw new WinGetRepairException(Resources.RepairAllUsersMessage); + } + } + } + } +} From e40bcebd23d501655d977a05fc0948dec59c863b Mon Sep 17 00:00:00 2001 From: MengAiDev Meng <3463526515@qq.com> Date: Fri, 29 Aug 2025 10:25:53 +0000 Subject: [PATCH 2/2] crlf --- .../AssertWinGetPackageManagerCmdlet.cs | 82 +-- .../Common/WinGetPackageManagerCmdlet.cs | 80 +-- .../RepairWinGetPackageManagerCmdlet.cs | 134 ++--- .../Commands/WinGetPackageManagerCommand.cs | 496 +++++++++--------- 4 files changed, 396 insertions(+), 396 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs index 712c464d26..4c68f32288 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Assert-WinGetPackageManager. Verifies winget is installed properly. - /// - [Cmdlet( - VerbsLifecycle.Assert, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [Alias("awgpm")] - public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet - { - /// - /// Validates winget is installed correctly. If not, throws an exception - /// with the reason why, if any. - /// - protected override void ProcessRecord() - { - var command = new WinGetPackageManagerCommand(this); - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - command.AssertUsingLatest(this.IncludePrerelease.ToBool()); - } - else - { - command.Assert(this.Version, this.IncludePrerelease.ToBool()); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Assert-WinGetPackageManager. Verifies winget is installed properly. + /// + [Cmdlet( + VerbsLifecycle.Assert, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [Alias("awgpm")] + public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + /// + /// Validates winget is installed correctly. If not, throws an exception + /// with the reason why, if any. + /// + protected override void ProcessRecord() + { + var command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + command.AssertUsingLatest(this.IncludePrerelease.ToBool()); + } + else + { + command.Assert(this.Version, this.IncludePrerelease.ToBool()); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs index 2977559cc2..7d2bd9121e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - - /// - /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. - /// - public abstract class WinGetPackageManagerCmdlet : PSCmdlet - { - /// - /// Gets or sets the optional version. - /// - [Parameter( - ParameterSetName = Constants.IntegrityVersionSet, - ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether to use latest. - /// - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter Latest { get; set; } - - /// - /// Gets or sets a value indicating whether to include prerelease winget versions. - /// - [Parameter( - ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludePrerelease { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + + /// + /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. + /// + public abstract class WinGetPackageManagerCmdlet : PSCmdlet + { + /// + /// Gets or sets the optional version. + /// + [Parameter( + ParameterSetName = Constants.IntegrityVersionSet, + ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether to use latest. + /// + [Parameter( + ParameterSetName = Constants.IntegrityLatestSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter Latest { get; set; } + + /// + /// Gets or sets a value indicating whether to include prerelease winget versions. + /// + [Parameter( + ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludePrerelease { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs index c53bac23e0..4939355878 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs @@ -1,67 +1,67 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Repair-WinGetPackageManager. Repairs winget if needed. - /// - [Cmdlet( - VerbsDiagnostic.Repair, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [Alias("rpwgpm")] - [OutputType(typeof(int))] - public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet - { - private WinGetPackageManagerCommand command = null; - - /// - /// Gets or sets a value indicating whether to repair for all users. Requires admin. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AllUsers { get; set; } - - /// - /// Gets or sets a value indicating whether to force application shutdown. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// - /// Attempts to repair winget. - /// TODO: consider WhatIf and Confirm options. - /// - protected override void ProcessRecord() - { - this.command = new WinGetPackageManagerCommand(this); - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - this.command.RepairUsingLatest(this.IncludePrerelease.ToBool(), this.AllUsers.ToBool(), this.Force.ToBool()); - } - else - { - this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool(), this.IncludePrerelease.ToBool()); - } - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Repair-WinGetPackageManager. Repairs winget if needed. + /// + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [Alias("rpwgpm")] + [OutputType(typeof(int))] + public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + private WinGetPackageManagerCommand command = null; + + /// + /// Gets or sets a value indicating whether to repair for all users. Requires admin. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllUsers { get; set; } + + /// + /// Gets or sets a value indicating whether to force application shutdown. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Attempts to repair winget. + /// TODO: consider WhatIf and Confirm options. + /// + protected override void ProcessRecord() + { + this.command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + this.command.RepairUsingLatest(this.IncludePrerelease.ToBool(), this.AllUsers.ToBool(), this.Force.ToBool()); + } + else + { + this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool(), this.IncludePrerelease.ToBool()); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index ab62170916..aa529cab98 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -1,248 +1,248 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Threading.Tasks; - 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.Common.Command; - using Microsoft.WinGet.Resources; - using static Microsoft.WinGet.Client.Engine.Common.Constants; - - /// - /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. - /// - public sealed class WinGetPackageManagerCommand : BaseCommand - { - private const string EnvPath = "env:PATH"; - - /// - /// Initializes a new instance of the class. - /// - /// Cmdlet being executed. - public WinGetPackageManagerCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Asserts winget version is the latest version on winget-cli. - /// - /// Use prerelease version on GitHub. - public void AssertUsingLatest(bool preRelease) - { - var runningTask = this.RunOnMTA( - async () => - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); - this.Assert(expectedVersion); - return true; - }); - - this.Wait(runningTask); - } - - /// - /// Asserts the version installed is the specified. - /// - /// The expected version. - /// Include prerelease versions when validating the specified version. - public void Assert(string expectedVersion, bool preRelease = false) - { - // The preRelease parameter is for consistency but not used in this method - // since we're checking for an exact version match - WinGetIntegrity.AssertWinGet(this, expectedVersion); - } - - /// - /// Repairs winget using the latest version on winget-cli. - /// - /// Use prerelease version on GitHub. - /// Install for all users. Requires admin. - /// Force application shutdown. - public void RepairUsingLatest(bool preRelease, bool allUsers, bool force) - { - this.ValidateWhenAllUsers(allUsers); - var runningTask = this.RunOnMTA( - async () => - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); - await this.RepairStateMachineAsync(expectedVersion, allUsers, force); - return true; - }); - - this.Wait(runningTask); - } - - /// - /// Repairs winget if needed. - /// - /// The expected version, if any. - /// Install for all users. Requires admin. - /// Force application shutdown. - /// Include prerelease versions when searching for the specified version. - public void Repair(string expectedVersion, bool allUsers, bool force, bool preRelease = false) - { - this.ValidateWhenAllUsers(allUsers); - var runningTask = this.RunOnMTA( - async () => - { - // If no specific version is provided and IncludePrerelease is specified, - // we need to get the latest prerelease version - if (string.IsNullOrEmpty(expectedVersion) && preRelease) - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(true); - } - - await this.RepairStateMachineAsync(expectedVersion, allUsers, force); - return true; - }); - this.Wait(runningTask); - } - - private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force) - { - var seenCategories = new HashSet(); - var cancellationToken = this.GetCancellationToken(); - - var currentCategory = IntegrityCategory.Unknown; - while (currentCategory != IntegrityCategory.Installed) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - WinGetIntegrity.AssertWinGet(this, expectedVersion); - this.Write(StreamType.Verbose, $"WinGet is in a good state."); - currentCategory = IntegrityCategory.Installed; - } - catch (WinGetIntegrityException e) - { - currentCategory = e.Category; - - if (seenCategories.Contains(currentCategory)) - { - this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); - throw; - } - - this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}"); - seenCategories.Add(currentCategory); - - switch (currentCategory) - { - case IntegrityCategory.UnexpectedVersion: - await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), allUsers, force); - break; - case IntegrityCategory.NotInPath: - this.RepairEnvPath(); - break; - case IntegrityCategory.AppInstallerNotRegistered: - this.Register(expectedVersion); - break; - case IntegrityCategory.AppInstallerNotInstalled: - case IntegrityCategory.AppInstallerNotSupported: - case IntegrityCategory.Failure: - await this.InstallAsync(expectedVersion, allUsers, force); - break; - case IntegrityCategory.AppInstallerNoLicense: - // This requires -AllUsers in admin mode. - if (allUsers && Utilities.ExecutingAsAdministrator) - { - await this.InstallAsync(expectedVersion, allUsers, force); - } - else - { - throw new WinGetRepairException(e); - } - - break; - case IntegrityCategory.AppExecutionAliasDisabled: - case IntegrityCategory.Unknown: - throw new WinGetRepairException(e); - default: - throw new NotSupportedException(); - } - } - } - } - - private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force) - { - var installedVersion = WinGetVersion.InstalledWinGetVersion(this); - bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; - - string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + - $"Installing WinGet version '{toInstallVersion.TagVersion}' " + - $"Is downgrade {isDowngrade}"; - this.Write( - StreamType.Verbose, - message); - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force); - } - - private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force) - { - // 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(toInstallVersion)) - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); - toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false); - } - - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force); - } - - private void Register(string toRegisterVersion) - { - var appxModule = new AppxModuleHelper(this); - appxModule.RegisterAppInstaller(toRegisterVersion); - } - - private void RepairEnvPath() - { - // Add windows app path to user PATH environment variable - Utilities.AddWindowsAppToPath(); - - // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. - string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); - string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); - string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; - this.SetVariable(EnvPath, newPwshPathEnv); - - this.Write(StreamType.Verbose, $"PATH environment variable updated"); - } - - private void ValidateWhenAllUsers(bool allUsers) - { - if (allUsers) - { - if (Utilities.ExecutingAsSystem) - { - throw new NotSupportedException(); - } - - if (!Utilities.ExecutingAsAdministrator) - { - throw new WinGetRepairException(Resources.RepairAllUsersMessage); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading.Tasks; + 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.Common.Command; + using Microsoft.WinGet.Resources; + using static Microsoft.WinGet.Client.Engine.Common.Constants; + + /// + /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. + /// + public sealed class WinGetPackageManagerCommand : BaseCommand + { + private const string EnvPath = "env:PATH"; + + /// + /// Initializes a new instance of the class. + /// + /// Cmdlet being executed. + public WinGetPackageManagerCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Asserts winget version is the latest version on winget-cli. + /// + /// Use prerelease version on GitHub. + public void AssertUsingLatest(bool preRelease) + { + var runningTask = this.RunOnMTA( + async () => + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); + this.Assert(expectedVersion); + return true; + }); + + this.Wait(runningTask); + } + + /// + /// Asserts the version installed is the specified. + /// + /// The expected version. + /// Include prerelease versions when validating the specified version. + public void Assert(string expectedVersion, bool preRelease = false) + { + // The preRelease parameter is for consistency but not used in this method + // since we're checking for an exact version match + WinGetIntegrity.AssertWinGet(this, expectedVersion); + } + + /// + /// Repairs winget using the latest version on winget-cli. + /// + /// Use prerelease version on GitHub. + /// Install for all users. Requires admin. + /// Force application shutdown. + public void RepairUsingLatest(bool preRelease, bool allUsers, bool force) + { + this.ValidateWhenAllUsers(allUsers); + var runningTask = this.RunOnMTA( + async () => + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); + await this.RepairStateMachineAsync(expectedVersion, allUsers, force); + return true; + }); + + this.Wait(runningTask); + } + + /// + /// Repairs winget if needed. + /// + /// The expected version, if any. + /// Install for all users. Requires admin. + /// Force application shutdown. + /// Include prerelease versions when searching for the specified version. + public void Repair(string expectedVersion, bool allUsers, bool force, bool preRelease = false) + { + this.ValidateWhenAllUsers(allUsers); + var runningTask = this.RunOnMTA( + async () => + { + // If no specific version is provided and IncludePrerelease is specified, + // we need to get the latest prerelease version + if (string.IsNullOrEmpty(expectedVersion) && preRelease) + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(true); + } + + await this.RepairStateMachineAsync(expectedVersion, allUsers, force); + return true; + }); + this.Wait(runningTask); + } + + private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force) + { + var seenCategories = new HashSet(); + var cancellationToken = this.GetCancellationToken(); + + var currentCategory = IntegrityCategory.Unknown; + while (currentCategory != IntegrityCategory.Installed) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + WinGetIntegrity.AssertWinGet(this, expectedVersion); + this.Write(StreamType.Verbose, $"WinGet is in a good state."); + currentCategory = IntegrityCategory.Installed; + } + catch (WinGetIntegrityException e) + { + currentCategory = e.Category; + + if (seenCategories.Contains(currentCategory)) + { + this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); + throw; + } + + this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}"); + seenCategories.Add(currentCategory); + + switch (currentCategory) + { + case IntegrityCategory.UnexpectedVersion: + await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), allUsers, force); + break; + case IntegrityCategory.NotInPath: + this.RepairEnvPath(); + break; + case IntegrityCategory.AppInstallerNotRegistered: + this.Register(expectedVersion); + break; + case IntegrityCategory.AppInstallerNotInstalled: + case IntegrityCategory.AppInstallerNotSupported: + case IntegrityCategory.Failure: + await this.InstallAsync(expectedVersion, allUsers, force); + break; + case IntegrityCategory.AppInstallerNoLicense: + // This requires -AllUsers in admin mode. + if (allUsers && Utilities.ExecutingAsAdministrator) + { + await this.InstallAsync(expectedVersion, allUsers, force); + } + else + { + throw new WinGetRepairException(e); + } + + break; + case IntegrityCategory.AppExecutionAliasDisabled: + case IntegrityCategory.Unknown: + throw new WinGetRepairException(e); + default: + throw new NotSupportedException(); + } + } + } + } + + private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force) + { + var installedVersion = WinGetVersion.InstalledWinGetVersion(this); + bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; + + string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + + $"Installing WinGet version '{toInstallVersion.TagVersion}' " + + $"Is downgrade {isDowngrade}"; + this.Write( + StreamType.Verbose, + message); + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force); + } + + private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force) + { + // 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(toInstallVersion)) + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli); + toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false); + } + + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force); + } + + private void Register(string toRegisterVersion) + { + var appxModule = new AppxModuleHelper(this); + appxModule.RegisterAppInstaller(toRegisterVersion); + } + + private void RepairEnvPath() + { + // Add windows app path to user PATH environment variable + Utilities.AddWindowsAppToPath(); + + // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. + string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); + string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); + string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; + this.SetVariable(EnvPath, newPwshPathEnv); + + this.Write(StreamType.Verbose, $"PATH environment variable updated"); + } + + private void ValidateWhenAllUsers(bool allUsers) + { + if (allUsers) + { + if (Utilities.ExecutingAsSystem) + { + throw new NotSupportedException(); + } + + if (!Utilities.ExecutingAsAdministrator) + { + throw new WinGetRepairException(Resources.RepairAllUsersMessage); + } + } + } + } +}