Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PowerShell cmdlets #2838

Merged
merged 11 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ PCCERT
PCs
pcwsz
PEGI
PFM
pfn
pfxpath
Pherson
Expand All @@ -263,6 +264,7 @@ processthreads
productcode
pscustomobject
pseudocode
PSHOST
psobject
ptstr
publickey
Expand All @@ -281,6 +283,7 @@ REFIID
regexes
REGSAM
relativefilepath
remoting
reparse
restsource
rgex
Expand Down
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);
}
}
}
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; }
}
}
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);
}
}
}
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");
}
}
}
47 changes: 45 additions & 2 deletions src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,51 @@ internal static class Constants
/// This parameter set indicates that a package was not provided via a parameter or the pipeline and it
/// needs to be found by searching a package source.
/// </summary>
public const string FoundSet = "FoundSet";

public const string FoundSet = "FoundSet";

/// <summary>
/// Parameter set for an specific version parameter.
/// </summary>
public const string IntegrityVersionSet = "IntegrityVersionSet";

/// <summary>
/// Parameter set for an latest version with optional prerelease version.
/// </summary>
public const string IntegrityLatestSet = "IntegrityLatestSet";

/// <summary>
/// WinGet package family name.
/// </summary>
#if USE_PROD_CLSIDS
public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe";
#else
public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe";
#endif

/// <summary>
/// Winget executable name.
/// </summary>
#if USE_PROD_CLSIDS
public const string WinGetExe = "winget.exe";
#else
public const string WinGetExe = "wingetdev.exe";
#endif

/// <summary>
/// Name of PATH environment variable.
/// </summary>
public const string PathEnvVar = "PATH";

/// <summary>
/// Nouns used for different cmdlets. Changing this will alter the names of the related commands.
/// </summary>
public static class WinGetNouns
{
/// <summary>
/// WinGet.
/// </summary>
public const string WinGetPackageManager = "WinGetPackageManager";

/// <summary>
/// The noun analogue of the <see cref="CatalogPackage" /> class.
/// </summary>
Expand All @@ -53,6 +91,11 @@ public static class WinGetNouns
/// The noun for any user settings cmdlet.
/// </summary>
public const string UserSettings = "WinGetUserSettings";

/// <summary>
/// The noun for winget version.
/// </summary>
public const string Version = "WinGetVersion";
}
}
}
Loading