-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement PowerShell cmdlets (#2838)
This PR implements two new PowerShelll cmdlets Assert-WinGetPackageManager and Repair-WinGetPackageManager as well as the WinGetPackageManager DSC Resource. Assert-WinGetPackageManager verifies the integrity of winget for the current user. If winget is not set up correctly, it will throw an exception with the type of failure if detected. It can detect: Failed call into winget --version. winget is not found because is not in the PATH environment variable. winget is not found because the App execution alias is disabled. Windows OS is not supported. The AppInstaller package is not installed in the machine. The AppInstaller package is not registered for the user. The AppInstaller package is old and doesn't contain winget. Assert-WinGetPackageManager -Version foo also verifies the installed version is the same as the expected. Assert-WinGetPackageManager -Latest also verifies the installed version is the latest release version. Assert-WinGetPackageManager -Latest -IncludePrerelease also verifies the installed version is the latest prerelease version. Repair-WinGetPackageManager does the same as Assert-WinGetIntegrity but attempts to fix winget depending on the failure. Returns 0 is succeeded. Repair-WinGetPackageManager repairs current winget installed and performs no updates. If winget is not installed or there's catastrophic failure then installs the latest released winget. Repair-WinGetPackageManager -Version foo repairs winget and makes sure that the current installed version is the expected one. Repair-WinGetPackageManager -Latest repairs winget and makes sure winget is the latest released version. Repair-WinGetPackageManager -Latest -IncludePreRelease repairs winget and makes sure winget is the latest prereleased version. It sadly can't repair if AppExecution alias is disabled because there's no programmatic way to do it (requires internal APIs). It also doesn't install Microsoft.UI.Xaml.2.7 as a dependency because the current solution to download the nuget package, extract it and install the appx within is not the right approach we want to follow. We will follow up internally with the owners of the package to do something similar as the VCLibs packages. There's still a lot of opportunity for Repair-WinGetPackageManager to make it more robust, but this PR is just the starting phase. The WinGetPackageManager DSC resource is just a simple wrapper around those two cmdlets. There's also a sample on how to use it. For now, all versions need to be the tag name of the releases in GitHub. Also, moved Get-WinGetVersion from Crescendo to a binary cmdlet. --------- Co-authored-by: JohnMcPMS <[email protected]>
- Loading branch information
1 parent
1898da0
commit fc215c4
Showing
30 changed files
with
2,106 additions
and
703 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
src/PowerShell/Microsoft.WinGet.Client/Commands/AssertWinGetPackageManagerCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// ----------------------------------------------------------------------------- | ||
// <copyright file="AssertWinGetPackageManagerCommand.cs" company="Microsoft Corporation"> | ||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License. | ||
// </copyright> | ||
// ----------------------------------------------------------------------------- | ||
|
||
namespace Microsoft.WinGet.Client.Commands | ||
{ | ||
using System.Management.Automation; | ||
using Microsoft.WinGet.Client.Commands.Common; | ||
using Microsoft.WinGet.Client.Common; | ||
using Microsoft.WinGet.Client.Helpers; | ||
|
||
/// <summary> | ||
/// Assert-WinGetPackageManager. Verifies winget is installed properly. | ||
/// </summary> | ||
[Cmdlet( | ||
VerbsLifecycle.Assert, | ||
Constants.WinGetNouns.WinGetPackageManager, | ||
DefaultParameterSetName = Constants.IntegrityVersionSet)] | ||
public class AssertWinGetPackageManagerCommand : BaseIntegrityCommand | ||
{ | ||
/// <summary> | ||
/// Validates winget is installed correctly. If not, throws an exception | ||
/// with the reason why, if any. | ||
/// </summary> | ||
protected override void ProcessRecord() | ||
{ | ||
string expectedVersion = string.Empty; | ||
if (this.ParameterSetName == Constants.IntegrityLatestSet) | ||
{ | ||
var gitHubRelease = new GitHubRelease(); | ||
expectedVersion = gitHubRelease.GetLatestVersionTagName(this.IncludePreRelease.ToBool()); | ||
} | ||
else | ||
{ | ||
expectedVersion = this.Version; | ||
} | ||
|
||
WinGetIntegrity.AssertWinGet(this, expectedVersion); | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// ----------------------------------------------------------------------------- | ||
// <copyright file="BaseIntegrityCommand.cs" company="Microsoft Corporation"> | ||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License. | ||
// </copyright> | ||
// ----------------------------------------------------------------------------- | ||
|
||
namespace Microsoft.WinGet.Client.Commands.Common | ||
{ | ||
using System.Management.Automation; | ||
using Microsoft.WinGet.Client.Common; | ||
|
||
/// <summary> | ||
/// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. | ||
/// </summary> | ||
public abstract class BaseIntegrityCommand : BaseCommand | ||
{ | ||
/// <summary> | ||
/// Gets or sets the optional version. | ||
/// </summary> | ||
[Parameter( | ||
ParameterSetName = Constants.IntegrityVersionSet, | ||
ValueFromPipelineByPropertyName = true)] | ||
public string Version { get; set; } = string.Empty; | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether to use latest. | ||
/// </summary> | ||
[Parameter( | ||
ParameterSetName = Constants.IntegrityLatestSet, | ||
ValueFromPipelineByPropertyName = true)] | ||
public SwitchParameter Latest { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether to include prerelease winget versions. | ||
/// </summary> | ||
[Parameter( | ||
ParameterSetName = Constants.IntegrityLatestSet, | ||
ValueFromPipelineByPropertyName = true)] | ||
public SwitchParameter IncludePreRelease { get; set; } | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// ----------------------------------------------------------------------------- | ||
// <copyright file="GetVersionCommand.cs" company="Microsoft Corporation"> | ||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License. | ||
// </copyright> | ||
// ----------------------------------------------------------------------------- | ||
|
||
namespace Microsoft.WinGet.Client.Commands | ||
{ | ||
using System.Management.Automation; | ||
using Microsoft.WinGet.Client.Commands.Common; | ||
using Microsoft.WinGet.Client.Common; | ||
using Microsoft.WinGet.Client.Helpers; | ||
|
||
/// <summary> | ||
/// Get-WinGetVersion. Gets the current version of winget. | ||
/// </summary> | ||
[Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] | ||
[OutputType(typeof(string))] | ||
public class GetVersionCommand : BaseCommand | ||
{ | ||
/// <summary> | ||
/// Writes the winget version. | ||
/// </summary> | ||
protected override void ProcessRecord() | ||
{ | ||
this.WriteObject(WinGetVersion.InstalledWinGetVersion.TagVersion); | ||
} | ||
} | ||
} |
183 changes: 183 additions & 0 deletions
183
src/PowerShell/Microsoft.WinGet.Client/Commands/RepairWinGetPackageManagerCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
// ----------------------------------------------------------------------------- | ||
// <copyright file="RepairWinGetPackageManagerCommand.cs" company="Microsoft Corporation"> | ||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License. | ||
// </copyright> | ||
// ----------------------------------------------------------------------------- | ||
|
||
namespace Microsoft.WinGet.Client.Commands | ||
{ | ||
using System; | ||
using System.Management.Automation; | ||
using Microsoft.WinGet.Client.Commands.Common; | ||
using Microsoft.WinGet.Client.Common; | ||
using Microsoft.WinGet.Client.Helpers; | ||
using Microsoft.WinGet.Client.Properties; | ||
|
||
/// <summary> | ||
/// Repair-WinGetPackageManager. Repairs winget if needed. | ||
/// </summary> | ||
[Cmdlet( | ||
VerbsDiagnostic.Repair, | ||
Constants.WinGetNouns.WinGetPackageManager, | ||
DefaultParameterSetName = Constants.IntegrityVersionSet)] | ||
[OutputType(typeof(int))] | ||
public class RepairWinGetPackageManagerCommand : BaseIntegrityCommand | ||
{ | ||
private const string EnvPath = "env:PATH"; | ||
private const int Succeeded = 0; | ||
private const int Failed = -1; | ||
|
||
private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; | ||
|
||
/// <summary> | ||
/// Attempts to repair winget. | ||
/// TODO: consider WhatIf and Confirm options. | ||
/// </summary> | ||
protected override void ProcessRecord() | ||
{ | ||
int result = Failed; | ||
|
||
string expectedVersion = this.Version; | ||
if (this.ParameterSetName == Constants.IntegrityLatestSet) | ||
{ | ||
var gitHubRelease = new GitHubRelease(); | ||
expectedVersion = gitHubRelease.GetLatestVersionTagName(this.IncludePreRelease.ToBool()); | ||
} | ||
|
||
var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, expectedVersion); | ||
this.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, expectedVersion); | ||
this.WriteDebug($"Integrity category after fixing PATH {newIntegrityCategory}"); | ||
result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); | ||
} | ||
else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) | ||
{ | ||
var appxModule = new AppxModuleHelper(this); | ||
appxModule.RegisterAppInstaller(); | ||
|
||
// Now try again and get the desired winget version if needed. | ||
var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this, expectedVersion); | ||
this.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.WriteDebug($"Failed installing {expectedVersion}"); | ||
} | ||
} | ||
else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) | ||
{ | ||
// Sorry, but the user has to manually enabled it. | ||
this.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); | ||
} | ||
else | ||
{ | ||
this.WriteInformation(Resources.WinGetNotSupportedMessage, WriteInformationTags); | ||
} | ||
|
||
this.WriteObject(result); | ||
} | ||
|
||
private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expectedVersion) | ||
{ | ||
if (integrityCategory == IntegrityCategory.Installed) | ||
{ | ||
// Nothing to do | ||
this.WriteDebug($"WinGet is in a good state."); | ||
return Succeeded; | ||
} | ||
else if (integrityCategory == IntegrityCategory.UnexpectedVersion) | ||
{ | ||
// The versions are different, download and install. | ||
if (!this.InstallDifferentVersion(new WinGetVersion(expectedVersion))) | ||
{ | ||
this.WriteDebug($"Failed installing {expectedVersion}"); | ||
} | ||
else | ||
{ | ||
return Succeeded; | ||
} | ||
} | ||
|
||
return Failed; | ||
} | ||
|
||
private bool InstallDifferentVersion(WinGetVersion toInstallVersion) | ||
{ | ||
var installedVersion = WinGetVersion.InstalledWinGetVersion; | ||
|
||
this.WriteDebug($"Installed WinGet version {installedVersion.TagVersion}"); | ||
this.WriteDebug($"Installing WinGet version {toInstallVersion.TagVersion}"); | ||
|
||
bool downgrade = false; | ||
if (installedVersion.CompareAsDeployment(toInstallVersion) > 0) | ||
{ | ||
downgrade = true; | ||
} | ||
|
||
return this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); | ||
} | ||
|
||
private bool DownloadAndInstall(string versionTag, bool downgrade) | ||
{ | ||
// Download and install. | ||
var gitHubRelease = new GitHubRelease(); | ||
var downloadedMsixBundlePath = gitHubRelease.DownloadRelease(versionTag); | ||
|
||
var appxModule = new AppxModuleHelper(this); | ||
appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); | ||
|
||
// Verify that is installed | ||
var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, versionTag); | ||
if (integrityCategory != IntegrityCategory.Installed) | ||
{ | ||
return false; | ||
} | ||
|
||
this.WriteDebug($"Installed WinGet version {versionTag}"); | ||
return true; | ||
} | ||
|
||
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.SessionState.PSVariable.Set(EnvPath, newPwshPathEnv); | ||
|
||
this.WriteDebug($"PATH environment variable updated"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.