From 8eea12bb95564b12228d8de495da9e3f7a647896 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 10 Jan 2023 15:56:14 -0800 Subject: [PATCH 01/11] merge tmp branch --- .../Commands/AssertIntegrityCommand.cs | 28 + .../Commands/GetVersionCommand.cs | 28 + .../Commands/RepairCommand.cs | 125 ++ .../Common/Constants.cs | 38 + .../Common/IntegrityCategory.cs | 59 + .../Common/Utilities.cs | 40 +- .../Common/WinGetIntegrity.cs | 128 ++ .../Crescendo/Crescendo.json | 11 - .../Exceptions/WinGetCLITimeoutException.cs | 27 + .../Exceptions/WinGetIntegrityException.cs | 56 + .../WinGetPackageNotInstalledException.cs | 26 - .../Helpers/AppxModuleHelper.cs | 156 +++ .../Helpers/ComObjectFactory.cs | 4 +- .../Helpers/GitHubRelease.cs | 139 ++ .../Helpers/WinGetVersionHelper.cs | 67 + .../Helpers/WingetCLIWrapper.cs | 55 +- .../Microsoft.WinGet.Client.csproj | 1 + .../Module/Microsoft.WinGet.Client.psd1 | 6 +- .../Module/Microsoft.WinGet.Client.psm1 | 1121 ++++++++--------- .../Properties/Resources.Designer.cs | 78 +- .../Properties/Resources.resx | 28 +- .../Microsoft.WinGet.DSC.psm1 | 47 + 22 files changed, 1594 insertions(+), 674 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLITimeoutException.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs delete mode 100644 src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetPackageNotInstalledException.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs new file mode 100644 index 0000000000..6e7b28cee9 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------------- +// +// 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; + + /// + /// Assert-WinGetIntegrity. Verifies winget is installed properly. + /// + [Cmdlet(VerbsLifecycle.Assert, Constants.WinGetNouns.Integrity)] + public class AssertIntegrityCommand : BaseCommand + { + /// + /// Validates winget is installed correctly. If not, throws an exception + /// with the reason why, if any. + /// + protected override void ProcessRecord() + { + WinGetIntegrity.AssertWinGet(this.InvokeCommand); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs new file mode 100644 index 0000000000..68306aafb7 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------------- +// +// 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.Helpers; + + /// + /// Get-WinGetVersion. Gets the current version of winget. + /// + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] + public class GetVersionCommand : BaseCommand + { + /// + /// Writes the winget version. + /// + protected override void ProcessRecord() + { + this.WriteObject(WinGetVersionHelper.InstalledWinGetVersion); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs new file mode 100644 index 0000000000..5631912d7a --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -0,0 +1,125 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +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; + + /// + /// Repair-WinGet. Repairs winget if needed. + /// + [Cmdlet(VerbsDiagnostic.Repair, Constants.WinGetNouns.WinGet)] + public class RepairCommand : BaseCommand + { + private const string EnvPath = "env:PATH"; + + /// + /// Gets or sets the optional version. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether to include prerelease winget versions. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludePreRelease { get; set; } + + /// + /// Attempts to repair winget. + /// TODO: consider WhatIf and Confirm options. + /// + protected override void ProcessRecord() + { + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this.InvokeCommand); + + bool preRelease = this.IncludePreRelease.ToBool(); + + if (integrityCategory == IntegrityCategory.Installed) + { + string toInstallVersion = this.Version; + var gitHubRelease = new GitHubRelease(); + + if (string.IsNullOrEmpty(toInstallVersion)) + { + toInstallVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + } + + if (toInstallVersion != WinGetVersionHelper.InstalledWinGetVersion) + { + this.WriteObject($"Current installed version {WinGetVersionHelper.InstalledWinGetVersion}"); + this.WriteObject($"Version to install {toInstallVersion}"); + + var downloadedMsixBundlePath = gitHubRelease.DownloadRelease( + preRelease, + toInstallVersion); + + var installedVersion = WinGetVersionHelper.ConvertInstalledWinGetVersion(); + var inputVersion = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); + + bool downgrade = false; + if (installedVersion.CompareTo(inputVersion) > 0) + { + downgrade = true; + } + + var appxModule = new AppxModuleHelper(this.InvokeCommand); + appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); + } + else + { + this.WriteObject("WinGet is in good state"); + } + } + else if (integrityCategory == IntegrityCategory.NotInPath) + { + // 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); + } + else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) + { + var appxModule = new AppxModuleHelper(this.InvokeCommand); + appxModule.RegisterAppInstaller(); + } + else if (integrityCategory == IntegrityCategory.AppInstallerNotInstalled || + integrityCategory == IntegrityCategory.AppInstallerNotSupported || + integrityCategory == IntegrityCategory.Failure) + { + // Download and install. + var gitHubRelease = new GitHubRelease(); + var downloadedMsixBundlePath = gitHubRelease.DownloadRelease( + this.IncludePreRelease.ToBool(), + this.Version); + + var appxModule = new AppxModuleHelper(this.InvokeCommand); + appxModule.AddAppInstallerBundle(downloadedMsixBundlePath); + + this.WriteObject($"Version to install {WinGetVersionHelper.InstalledWinGetVersion}"); + } + else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) + { + // Sorry, but the user has to manually enabled it. + throw new Exception("app installer"); + } + else + { + // Unknown + // OsNotSupported + throw new Exception("impossible"); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs index a165353fa4..92ea69f321 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs @@ -34,11 +34,39 @@ internal static class Constants /// public const string FoundSet = "FoundSet"; + /// + /// WinGet package family name. + /// +#if USE_PROD_CLSIDS + public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; +#else + public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; +#endif + + /// + /// Winget executable name. + /// +#if USE_PROD_CLSIDS + public const string WinGetExe = "winget.exe"; +#else + public const string WinGetExe = "wingetdev.exe"; +#endif + + /// + /// Name of PATH environment variable. + /// + public const string PathEnvVar = "PATH"; + /// /// Nouns used for different cmdlets. Changing this will alter the names of the related commands. /// public static class WinGetNouns { + /// + /// WinGet. + /// + public const string WinGet = "WinGet"; + /// /// The noun analogue of the class. /// @@ -53,6 +81,16 @@ public static class WinGetNouns /// The noun for any user settings cmdlet. /// public const string UserSettings = "WinGetUserSettings"; + + /// + /// The noun for winget version. + /// + public const string Version = "WinGetVersion"; + + /// + /// The noun for winget integrity. + /// + public const string Integrity = "WinGetIntegrity"; } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs new file mode 100644 index 0000000000..eb4733404b --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs @@ -0,0 +1,59 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Common +{ + /// + /// The type of the integrity check failure. + /// + public enum IntegrityCategory + { + /// + /// WinGet is correctly installed. + /// + Installed, + + /// + /// Unknown reason. + /// + Unknown, + + /// + /// A failure resulted on a simple winget command that shouldn't happen. + /// + Failure, + + /// + /// WindowsAppPath not in PATH environment variable. + /// + NotInPath, + + /// + /// Winget's app execution alias disabled. + /// + AppExecutionAliasDisabled, + + /// + /// Windows OS is not supported. + /// + OsNotSupported, + + /// + /// AppInstaller package is not installed. + /// + AppInstallerNotInstalled, + + /// + /// AppInstaller package is not registered. + /// + AppInstallerNotRegistered, + + /// + /// Installed App Installer package is not supported. + /// + AppInstallerNotSupported, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs index 2fb27efe91..7b4de03367 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs @@ -6,7 +6,7 @@ namespace Microsoft.WinGet.Client.Common { - using System.Resources; + using System; using System.Security.Principal; /// @@ -41,5 +41,43 @@ public static bool ExecutingAsSystem return principal.IsInRole(WindowsBuiltInRole.SystemOperator); } } + + /// + /// Gets the windows app path for local app data. + /// + public static string LocalDataWindowsAppPath + { + get + { + return Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\WindowsApps"); + } + } + + /// + /// Gets the windows app path for program files. + /// + public static string ProgramFilesWindowsAppPath + { + get + { + return Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WindowsApps"); + } + } + + /// + /// Adds the WindowsApp local app data path to the user environment path. + /// + public static void AddWindowsAppToPath() + { + var scope = EnvironmentVariableTarget.User; + string envPathValue = Environment.GetEnvironmentVariable(Constants.PathEnvVar, scope); + if (string.IsNullOrEmpty(envPathValue) || !envPathValue.Contains(Utilities.LocalDataWindowsAppPath)) + { + Environment.SetEnvironmentVariable( + Constants.PathEnvVar, + $"{envPathValue};{Utilities.LocalDataWindowsAppPath}", + scope); + } + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs new file mode 100644 index 0000000000..89af4d1c4d --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -0,0 +1,128 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Common +{ + using System; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Management.Automation; + using Microsoft.WinGet.Client.Exceptions; + using Microsoft.WinGet.Client.Helpers; + + /// + /// Validates winget runs correctly. + /// + internal static class WinGetIntegrity + { + /// + /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. + /// + /// CommandInvocationIntrinsics of the calling cmdlet. + public static void AssertWinGet(CommandInvocationIntrinsics commandInvocation) + { + try + { + // Start by calling winget without its WindowsApp PFN path. + // If it succeeds and the exit code is 0 then we are good. + var wingetCliWrapper = new WingetCLIWrapper(false); + var result = wingetCliWrapper.RunCommand("--version"); + result.VerifyExitCode(); + } + catch (Win32Exception) + { + throw new WinGetIntegrityException(GetReason(commandInvocation)); + } + catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) + { + throw new WinGetIntegrityException(IntegrityCategory.Failure, e); + } + catch (Exception e) + { + throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); + } + } + + /// + /// Verifies winget runs correctly. + /// + /// CommandInvocationIntrinsics of the calling cmdlet. + /// Integrity category. + public static IntegrityCategory GetIntegrityCategory(CommandInvocationIntrinsics commandInvocation) + { + try + { + AssertWinGet(commandInvocation); + } + catch (WinGetIntegrityException e) + { + return e.Category; + } + + return IntegrityCategory.Installed; + } + + private static IntegrityCategory GetReason(CommandInvocationIntrinsics commandInvocation) + { + // Ok, so you are here because calling winget --version failed. Lets try to figure out why. + + // First lets check if the file is there, which means it is installed or someone is taking our place. + if (File.Exists(WingetCLIWrapper.WinGetFullPath)) + { + // The file exists, but we couldn't call it... We'll maybe winget's app execution alias is not enabled. + // The trick is knowing that a magical file appears under WindowsApp when its enabled. + string wingetAliasPath = Path.Combine(Utilities.LocalDataWindowsAppPath, Constants.WinGetExe); + if (File.Exists(wingetAliasPath)) + { + // App execution alias is enabled. Then maybe the path? + string envPath = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); + if (string.IsNullOrEmpty(envPath) || + !envPath.EndsWith(Utilities.LocalDataWindowsAppPath) || + !envPath.Contains($"{Utilities.LocalDataWindowsAppPath};")) + { + return IntegrityCategory.NotInPath; + } + } + else + { + return IntegrityCategory.AppExecutionAliasDisabled; + } + } + + // Not under %LOCALAPPDATA%\\Microsoft\\WindowsApps\PFM\ + + // Windows version has to be equal or newer than 10.0.17763.0 + var minWindowsVersion = new Version(10, 0, 17763, 0); + var osVersion = Environment.OSVersion.Version; + if (osVersion.CompareTo(minWindowsVersion) < 0) + { + return IntegrityCategory.OsNotSupported; + } + + // It could be that AppInstaller package is old or the package is not + // registered at this point. To know that, call Get-AppxPackage. + var appxModule = new AppxModuleHelper(commandInvocation); + string version = appxModule.GetAppInstallerPropertyValue("Version"); + if (version is null) + { + // This can happen in Windows Sandbox. + return IntegrityCategory.AppInstallerNotInstalled; + } + + // Now AppInstaller version has to be greater than 1.11.11451 + var minAppInstallerVersion = new Version(1, 11, 11451); + var appInstallerVersion = new Version(version); + if (appInstallerVersion.CompareTo(minAppInstallerVersion) < 0) + { + return IntegrityCategory.AppInstallerNotSupported; + } + + // If we get here, we know the package is in the machine but not registered for the user. + return IntegrityCategory.AppInstallerNotRegistered; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Crescendo.json b/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Crescendo.json index 212d4e2e39..35186772f7 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Crescendo.json +++ b/src/PowerShell/Microsoft.WinGet.Client/Crescendo/Crescendo.json @@ -1,17 +1,6 @@ { "$schema": "https://aka.ms/PowerShell/Crescendo/Schemas/2021-11", "Commands": [ - { - "Verb": "Get", - "Noun": "WinGetVersion", - "Platform": [ - "Windows" - ], - "OriginalName": "winget.exe", - "OriginalCommandElements": [ - "--version" - ] - }, { "Verb": "Enable", "Noun": "WinGetSetting", diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLITimeoutException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLITimeoutException.cs new file mode 100644 index 0000000000..8ad5ff7e04 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLITimeoutException.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Exceptions +{ + using System; + using Microsoft.WinGet.Client.Properties; + + /// + /// Time out exception for a winget cli command. + /// + public class WinGetCLITimeoutException : TimeoutException + { + /// + /// Initializes a new instance of the class. + /// + /// Command. + /// Parameters. + public WinGetCLITimeoutException(string command, string parameters) + : base(string.Format(Resources.WinGetCLITimeoutExceptionMessage, command, parameters)) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs new file mode 100644 index 0000000000..319540a911 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Exceptions +{ + using System; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Properties; + + /// + /// WinGet Integrity exception. + /// + public class WinGetIntegrityException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Category failure. + public WinGetIntegrityException(IntegrityCategory category) + : base(GetMessage(category)) + { + this.Category = category; + } + + /// + /// Initializes a new instance of the class. + /// + /// Category failure. + /// Inner exception. + public WinGetIntegrityException(IntegrityCategory category, Exception inner) + : base(GetMessage(category), inner) + { + this.Category = category; + } + + /// + /// Gets the category of the integrity failure. + /// + public IntegrityCategory Category { get; } + + private static string GetMessage(IntegrityCategory category) => category switch + { + IntegrityCategory.Failure => Resources.IntegrityFailureMessage, + IntegrityCategory.NotInPath => Resources.IntegrityNotInPathMessage, + IntegrityCategory.AppExecutionAliasDisabled => Resources.IntegrityAppExecutionAliasDisabledMessage, + IntegrityCategory.OsNotSupported => Resources.IntegrityOsNotSupportedMessage, + IntegrityCategory.AppInstallerNotInstalled => Resources.IntegrityAppInstallerNotInstalledMessage, + IntegrityCategory.AppInstallerNotRegistered => Resources.IntegrityAppInstallerNotRegisteredMessage, + IntegrityCategory.AppInstallerNotSupported => Resources.IntegrityAppInstallerNotSupportedMessage, + _ => Resources.IntegrityUnknownMessage, + }; + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetPackageNotInstalledException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetPackageNotInstalledException.cs deleted file mode 100644 index 3b5a2432e6..0000000000 --- a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetPackageNotInstalledException.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Properties; - - /// - /// No package found. - /// - [Serializable] - public class WinGetPackageNotInstalledException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public WinGetPackageNotInstalledException() - : base(Resources.WinGetPackageNotInstalledMessage) - { - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs new file mode 100644 index 0000000000..4085ec44d9 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -0,0 +1,156 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Helpers +{ + using System; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Runtime.InteropServices; + using System.Text; + using Microsoft.WinGet.Client.Common; + + /// + /// Helper to make calls to the Appx module. + /// There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without + /// the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between + /// using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session. + /// https://github.com/PowerShell/PowerShell/issues/13138. + /// + internal class AppxModuleHelper + { + private const string ImportModuleCore = "Import-Module Appx -UseWindowsPowerShell"; + private const string GetAppxPackageCommand = "Get-AppxPackage {0}"; + private const string AddAppxPackageFormat = "Add-AppxPackage -Path {0}"; + private const string AppxManifest = "AppxManifest.xml"; + + private const string ForceUpdateFromAnyVersion = " -ForceUpdateFromAnyVersion"; + private const string Register = "-Register"; + private const string DisableDevelopmentMode = "-DisableDevelopmentMode"; + + private const string AppInstallerName = "Microsoft.DesktopAppInstaller"; + + private readonly CommandInvocationIntrinsics commandInvocation; + + /// + /// Initializes a new instance of the class. + /// + /// From calling cmdlet. + public AppxModuleHelper(CommandInvocationIntrinsics commandInvocation) + { + this.commandInvocation = commandInvocation; + +#if !POWERSHELL_WINDOWS + this.commandInvocation.InvokeScript(ImportModuleCore); +#endif + } + + /// + /// Calls Get-AppxPackage Microsoft.DesktopAppInstaller. + /// + /// Result of Get-AppxPackage. + public PSObject GetAppInstallerObject() + { + return this.GetAppxObject(AppInstallerName); + } + + /// + /// Gets the string value a property from the Get-AppxPackage object of AppInstaller. + /// + /// Property name. + /// Value, null if doesn't exist. + public string GetAppInstallerPropertyValue(string propertyName) + { + string result = null; + var packageObj = this.GetAppInstallerObject(); + if (packageObj is not null) + { + var property = packageObj.Properties.Where(p => p.Name == propertyName).FirstOrDefault(); + if (property is not null) + { + result = property.Value as string; + } + } + + return result; + } + + /// + /// Calls Add-AppxPackage with the path specified. + /// + /// The path of the package to add. + /// If the package version is lower than the installed one. + public void AddAppInstallerBundle(string localPath, bool downgrade = false) + { + // TODO: We need to follow up for Microsoft.UI.Xaml.2.7 + // downloading the nuget and extracting it doesn't sound like the right thing to do. + this.InstallVCLibsDependencies(); + + StringBuilder sb = new StringBuilder(); + sb.Append(string.Format(AddAppxPackageFormat, localPath)); + + if (downgrade) + { + sb.Append(ForceUpdateFromAnyVersion); + } + + this.commandInvocation.InvokeScript(sb.ToString()); + } + + /// + /// Calls Add-AppxPackage to register. + /// + public void RegisterAppInstaller() + { + string packageFullName = this.GetAppInstallerPropertyValue("PackageFullName"); + string appxManifestPath = Path.Combine( + Utilities.ProgramFilesWindowsAppPath, + packageFullName, + AppxManifest); + + this.commandInvocation.InvokeScript( + $"{string.Format(AddAppxPackageFormat, appxManifestPath)} {Register} {DisableDevelopmentMode}"); + } + + private PSObject GetAppxObject(string packageName) + { + return this.commandInvocation + .InvokeScript(string.Format(GetAppxPackageCommand, packageName)) + .FirstOrDefault(); + } + + private void InstallVCLibsDependencies() + { + var vcLibsPackageObj = this.GetAppxObject("Microsoft.VCLibs.140.00.UWPDesktop_8wekyb3d8bbwe"); + if (vcLibsPackageObj is null) + { + var arch = RuntimeInformation.OSArchitecture; + string url; + if (arch == Architecture.X64) + { + url = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"; + } + else if (arch == Architecture.X86) + { + url = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx"; + } + else + { + throw new PSNotSupportedException(arch.ToString()); + } + + var tmpFile = Path.GetTempFileName(); + + // This is weird but easy. + var githubRelease = new GitHubRelease(); + githubRelease.DownloadUrl(url, tmpFile); + + this.commandInvocation.InvokeScript(string.Format(AddAppxPackageFormat, tmpFile)); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs index 98a28fbfed..ccd28ccb41 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs @@ -9,7 +9,7 @@ namespace Microsoft.WinGet.Client.Factories using System; using System.Runtime.InteropServices; using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Common; using Microsoft.WinGet.Client.Exceptions; #if NET @@ -123,7 +123,7 @@ private static T Create(Type type, in Guid iid) { if (hr == ErrorCode.FileNotFound) { - throw new WinGetPackageNotInstalledException(); + throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); } else { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs new file mode 100644 index 0000000000..b5663fc9fd --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs @@ -0,0 +1,139 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Helpers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using Octokit; + using FileMode = System.IO.FileMode; + + /// + /// Handles WinGet's releases in GitHub. + /// + internal class GitHubRelease + { + private const string Owner = "microsoft"; + private const string Repo = "winget-cli"; + private const string UserAgent = "winget-powershell"; + private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; + private const string ContentType = "application/octet-stream"; + + private readonly IGitHubClient gitHubClient; + + /// + /// Initializes a new instance of the class. + /// + public GitHubRelease() + { + this.gitHubClient = new GitHubClient(new ProductHeaderValue(UserAgent)); + } + + /// + /// Download a release from winget-cli. + /// + /// Include prerelease. + /// Optional release name. If null, gets latest. + /// Path where the msix bundle is downloaded. + public string DownloadRelease(bool includePreRelease, string releaseTag = null) + { + return this.DownloadReleaseAsync(includePreRelease, releaseTag).GetAwaiter().GetResult(); + } + + /// + /// Gets the latest released version and waits. + /// + /// Include prerelease. + /// Latest version. + public string GetLatestVersionTagName(bool includePreRelease) + { + return this.GetLatestVersionAsync(includePreRelease).GetAwaiter().GetResult().TagName; + } + + /// + /// Downloads a file from a url and waits. + /// + /// Url. + /// File name. + public void DownloadUrl(string url, string fileName) + { + this.DownloadUrlAsync(url, fileName).GetAwaiter().GetResult(); + } + + /// + /// Download asynchronously a release from winget-cli. + /// + /// Include prerelease. + /// Optional release name. If null, gets latest. + /// Path where the msix bundle is downloaded. + public async Task DownloadReleaseAsync(bool includePreRelease, string releaseTag = null) + { + Release release; + + // If not specified get the latest. + if (string.IsNullOrEmpty(releaseTag)) + { + release = await this.GetLatestVersionAsync(includePreRelease); + } + else + { + release = await this.gitHubClient.Repository.Release.Get(Owner, Repo, releaseTag); + } + + // Get asset and download. + var msixBundleAsset = release.Assets.Where(a => a.Name == MsixBundleName).First(); + + var tmpFile = Path.GetTempFileName(); + await this.DownloadUrlAsync(msixBundleAsset.Url, tmpFile); + return tmpFile; + } + + /// + /// Downloads a file from a url. + /// + /// Url. + /// File name. + /// A representing the asynchronous operation. + public async Task DownloadUrlAsync(string url, string fileName) + { + var response = await this.gitHubClient.Connection.Get( + new Uri(url), + new Dictionary(), + ContentType); + + using var memoryStream = new MemoryStream((byte[])response.Body); + using var fileStream = File.Open(fileName, FileMode.Open); + memoryStream.Position = 0; + await memoryStream.CopyToAsync(fileStream); + } + + /// + /// Gets the latest released version. + /// + /// Include prerelease. + /// Latest version. + internal async Task GetLatestVersionAsync(bool includePreRelease) + { + Release release; + + // GetLatest doesn't respect prerelease or gives an option to get it. + if (includePreRelease) + { + // GetAll orders by newest and includes pre releases. + release = (await this.gitHubClient.Repository.Release.GetAll(Owner, Repo))[0]; + } + else + { + release = await this.gitHubClient.Repository.Release.GetLatest(Owner, Repo); + } + + return release; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs new file mode 100644 index 0000000000..1acb6c2766 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs @@ -0,0 +1,67 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Helpers +{ + using System; + + /// + /// WinGetVersion helper. + /// + internal static class WinGetVersionHelper + { + /// + /// Gets the version of the installed winget. + /// + public static string InstalledWinGetVersion + { + get + { + var wingetCliWrapper = new WingetCLIWrapper(); + var result = wingetCliWrapper.RunCommand("--version"); + return result.StdOut.Replace(Environment.NewLine, string.Empty); + } + } + + /// + /// Converts installed WinGet string version to a Version object. + /// + /// Version. + public static Version ConvertInstalledWinGetVersion() + { + return ConvertWinGetVersion(InstalledWinGetVersion); + } + + /// + /// Converts a WinGet string format version to a Version object. + /// + /// Version string. + /// Version. + public static Version ConvertWinGetVersion(string version) + { + if (string.IsNullOrEmpty(version)) + { + throw new ArgumentNullException(); + } + + // WinGet version starts with v + if (version[0] != 'v') + { + throw new ArgumentException(); + } + + version = version.Substring(1); + + // WinGet version might end with -preview + if (version.EndsWith("-preview")) + { + version = version.Substring(0, version.IndexOf('-')); + } + + return Version.Parse(version); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs index 8e212d0893..56ed83f0e9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs @@ -6,9 +6,9 @@ namespace Microsoft.WinGet.Client.Helpers { - using System; using System.Diagnostics; using System.IO; + using Microsoft.WinGet.Client.Common; using Microsoft.WinGet.Client.Exceptions; /// @@ -16,40 +16,43 @@ namespace Microsoft.WinGet.Client.Helpers /// internal class WingetCLIWrapper { - private static readonly string WingetCliPath; + /// + /// The file name to use in start info. + /// + private readonly string wingetPath; /// - /// Initializes static members of the class. + /// Initializes a new instance of the class. /// When app execution alias is disabled the path of the exe is /// in the package family name directory in the local app data windows app directory. If its enabled then there's /// link in the windows app data directory. To avoid checking if its enabled or not, just look in the package /// family name directory. /// For test, point to the wingetdev executable. /// - static WingetCLIWrapper() + /// Use full path or not. + public WingetCLIWrapper(bool fullPath = true) { - string windowsAppPath = Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%\\Microsoft\\WindowsApps"); -#if USE_PROD_CLSIDS - WingetCliPath = Path.Combine( - windowsAppPath, - "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe", - "winget.exe"); -#else - WingetCliPath = Path.Combine( - windowsAppPath, - "WinGetDevCLI_8wekyb3d8bbwe", - "wingetdev.exe"); -#endif + if (fullPath) + { + this.wingetPath = WinGetFullPath; + } + else + { + this.wingetPath = Constants.WinGetExe; + } } /// - /// Initializes a new instance of the class. + /// Gets the full path of winget executable. /// - public WingetCLIWrapper() + public static string WinGetFullPath { - if (!File.Exists(WingetCliPath)) + get { - throw new WinGetPackageNotInstalledException(); + return Path.Combine( + Utilities.LocalDataWindowsAppPath, + Constants.WingetPackageFamilyName, + Constants.WinGetExe); } } @@ -60,11 +63,17 @@ public WingetCLIWrapper() /// Parameters. /// Time out. /// WinGetCommandResult. - public WinGetCLICommandResult RunCommand(string command, string parameters, int timeOut = 60000) + public WinGetCLICommandResult RunCommand(string command, string parameters = null, int timeOut = 60000) { + string args = command; + if (string.IsNullOrEmpty(parameters)) + { + args += ' ' + parameters; + } + Process p = new () { - StartInfo = new (WingetCliPath, command + ' ' + parameters) + StartInfo = new (this.wingetPath, args) { UseShellExecute = false, RedirectStandardOutput = true, @@ -84,7 +93,7 @@ public WinGetCLICommandResult RunCommand(string command, string parameters, int p.StandardError.ReadToEnd()); } - throw new TimeoutException($"Direct winget command run timed out: {command} {parameters}"); + throw new WinGetCLITimeoutException(command, parameters); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj b/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj index dd4a0e3290..3aa20b5fdf 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj +++ b/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj @@ -26,6 +26,7 @@ + all diff --git a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 index badf730a06..20e1a2c8c9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 @@ -75,7 +75,6 @@ else { # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( - 'Get-WinGetVersion' 'Enable-WinGetSetting', 'Disable-WinGetSetting', 'Add-WinGetSource', @@ -86,6 +85,7 @@ FunctionsToExport = @( # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @( + 'Get-WinGetVersion' 'Find-WinGetPackage', 'Get-WinGetPackage', 'Get-WinGetSource', @@ -94,7 +94,9 @@ CmdletsToExport = @( 'Update-WinGetPackage', 'Get-WinGetUserSettings', 'Set-WinGetUserSettings', - 'Test-WinGetUserSettings' + 'Test-WinGetUserSettings', + 'Assert-WinGetIntegrity', + 'Repair-WinGet' ) # Variables to export from this module diff --git a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psm1 b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psm1 index 497d9c09e1..859cac34df 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psm1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psm1 @@ -1,123 +1,37 @@ # Module created by Microsoft.PowerShell.Crescendo class PowerShellCustomFunctionAttribute : System.Attribute { - [bool]$RequiresElevation - [string]$Source - PowerShellCustomFunctionAttribute() { $this.RequiresElevation = $false; $this.Source = "Microsoft.PowerShell.Crescendo" } - PowerShellCustomFunctionAttribute([bool]$rElevation) { - $this.RequiresElevation = $rElevation - $this.Source = "Microsoft.PowerShell.Crescendo" - } + [bool]$RequiresElevation + [string]$Source + PowerShellCustomFunctionAttribute() { $this.RequiresElevation = $false; $this.Source = "Microsoft.PowerShell.Crescendo" } + PowerShellCustomFunctionAttribute([bool]$rElevation) { + $this.RequiresElevation = $rElevation + $this.Source = "Microsoft.PowerShell.Crescendo" + } } <# - .SYNOPSIS - Displays the version of the tool. +.SYNOPSIS +Enables the WinGet setting specified by the `Name` parameter. - .DESCRIPTION - Displays the version of the winget.exe tool. +.DESCRIPTION +Enables the WinGet setting specified by the `Name` parameter. +Supported settings: + - LocalManifestFiles + - BypassCertificatePinningForMicrosoftStore + - InstallerHashOverride + - LocalArchiveMalwareScanOverride - .INPUTS - None. +.PARAMETER Name +Specifies the name of the setting to be enabled. - .OUTPUTS - None +.INPUTS +None. - .EXAMPLE - PS> Get-WinGetVersion -#> -function Get-WinGetVersion -{ -[PowerShellCustomFunctionAttribute(RequiresElevation=$False)] -[CmdletBinding(SupportsShouldProcess)] - -param( ) +.OUTPUTS +None -BEGIN { - $__PARAMETERMAP = @{} - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } -} - -PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += '--version' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS -} - -<# - .SYNOPSIS - Enables the WinGet setting specified by the `Name` parameter. - - .DESCRIPTION - Enables the WinGet setting specified by the `Name` parameter. - Supported settings: - - LocalManifestFiles - - BypassCertificatePinningForMicrosoftStore - - InstallerHashOverride - - LocalArchiveMalwareScanOverride - - .PARAMETER Name - Specifies the name of the setting to be enabled. - - .INPUTS - None. - - .OUTPUTS - None - - .EXAMPLE - PS> Enable-WinGetSetting -name LocalManifestFiles +.EXAMPLE +PS> Enable-WinGetSetting -name LocalManifestFiles #> function Enable-WinGetSetting { @@ -127,105 +41,105 @@ function Enable-WinGetSetting param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [string]$Name - ) + ) BEGIN { - $__PARAMETERMAP = @{ - Name = @{ - OriginalName = '' - OriginalPosition = '0' - Position = '0' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - } - - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } + $__PARAMETERMAP = @{ + Name = @{ + OriginalName = '' + OriginalPosition = '0' + Position = '0' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + } + + $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } } PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += 'settings' - $__commandArgs += '--enable' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS + $__boundParameters = $PSBoundParameters + $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name + $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) + $__commandArgs = @() + $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) + if ($__boundParameters["Debug"]){wait-debugger} + $__commandArgs += 'settings' + $__commandArgs += '--enable' + foreach ($paramName in $__boundParameters.Keys| + Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| + Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { + $value = $__boundParameters[$paramName] + $param = $__PARAMETERMAP[$paramName] + if ($param) { + if ($value -is [switch]) { + if ($value.IsPresent) { + if ($param.OriginalName) { $__commandArgs += $param.OriginalName } + } + elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } + } + elseif ( $param.NoGap ) { + $pFmt = "{0}{1}" + if($value -match "\s") { $pFmt = "{0}""{1}""" } + $__commandArgs += $pFmt -f $param.OriginalName, $value + } + else { + if($param.OriginalName) { $__commandArgs += $param.OriginalName } + $__commandArgs += $value | Foreach-Object {$_} + } + } + } + $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} + if ($__boundParameters["Debug"]){wait-debugger} + if ( $__boundParameters["Verbose"]) { + Write-Verbose -Verbose -Message winget.exe + $__commandArgs | Write-Verbose -Verbose + } + $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] + if (! $__handlerInfo ) { + $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present + } + $__handler = $__handlerInfo.Handler + if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { + # check for the application and throw if it cannot be found + if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { + throw "Cannot find executable 'winget.exe'" + } + if ( $__handlerInfo.StreamOutput ) { + & "winget.exe" $__commandArgs | & $__handler + } + else { + $result = & "winget.exe" $__commandArgs + & $__handler $result + } + } +} # end PROCESS } <# - .SYNOPSIS - Disables the WinGet setting specified by the `Name` parameter. +.SYNOPSIS +Disables the WinGet setting specified by the `Name` parameter. - .DESCRIPTION - Disables the WinGet setting specified by the `Name` parameter. - Supported settings: - - LocalManifestFiles - - BypassCertificatePinningForMicrosoftStore - - InstallerHashOverride - - LocalArchiveMalwareScanOverride +.DESCRIPTION +Disables the WinGet setting specified by the `Name` parameter. +Supported settings: + - LocalManifestFiles + - BypassCertificatePinningForMicrosoftStore + - InstallerHashOverride + - LocalArchiveMalwareScanOverride - .PARAMETER Name - Specifies the name of the setting to be disabled. +.PARAMETER Name +Specifies the name of the setting to be disabled. - .INPUTS - None. +.INPUTS +None. - .OUTPUTS - None +.OUTPUTS +None - .EXAMPLE - PS> Disable-WinGetSetting -name LocalManifestFiles +.EXAMPLE +PS> Disable-WinGetSetting -name LocalManifestFiles #> function Disable-WinGetSetting { @@ -235,100 +149,100 @@ function Disable-WinGetSetting param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [string]$Name - ) + ) BEGIN { - $__PARAMETERMAP = @{ - Name = @{ - OriginalName = '' - OriginalPosition = '0' - Position = '0' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - } - - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } + $__PARAMETERMAP = @{ + Name = @{ + OriginalName = '' + OriginalPosition = '0' + Position = '0' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + } + + $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } } PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += 'settings' - $__commandArgs += '--disable' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS + $__boundParameters = $PSBoundParameters + $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name + $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) + $__commandArgs = @() + $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) + if ($__boundParameters["Debug"]){wait-debugger} + $__commandArgs += 'settings' + $__commandArgs += '--disable' + foreach ($paramName in $__boundParameters.Keys| + Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| + Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { + $value = $__boundParameters[$paramName] + $param = $__PARAMETERMAP[$paramName] + if ($param) { + if ($value -is [switch]) { + if ($value.IsPresent) { + if ($param.OriginalName) { $__commandArgs += $param.OriginalName } + } + elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } + } + elseif ( $param.NoGap ) { + $pFmt = "{0}{1}" + if($value -match "\s") { $pFmt = "{0}""{1}""" } + $__commandArgs += $pFmt -f $param.OriginalName, $value + } + else { + if($param.OriginalName) { $__commandArgs += $param.OriginalName } + $__commandArgs += $value | Foreach-Object {$_} + } + } + } + $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} + if ($__boundParameters["Debug"]){wait-debugger} + if ( $__boundParameters["Verbose"]) { + Write-Verbose -Verbose -Message winget.exe + $__commandArgs | Write-Verbose -Verbose + } + $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] + if (! $__handlerInfo ) { + $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present + } + $__handler = $__handlerInfo.Handler + if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { + # check for the application and throw if it cannot be found + if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { + throw "Cannot find executable 'winget.exe'" + } + if ( $__handlerInfo.StreamOutput ) { + & "winget.exe" $__commandArgs | & $__handler + } + else { + $result = & "winget.exe" $__commandArgs + & $__handler $result + } + } +} # end PROCESS } <# - .SYNOPSIS - Get winget settings. +.SYNOPSIS +Get winget settings. - .DESCRIPTION - Get the administrator settings values as well as the location of the user settings as json string +.DESCRIPTION +Get the administrator settings values as well as the location of the user settings as json string - .PARAMETER Name - None +.PARAMETER Name +None - .INPUTS - None. +.INPUTS +None. - .OUTPUTS - Prints the export settings json. +.OUTPUTS +Prints the export settings json. - .EXAMPLE - PS> Get-WinGetSettings +.EXAMPLE +PS> Get-WinGetSettings #> function Get-WinGetSettings { @@ -338,94 +252,94 @@ function Get-WinGetSettings param( ) BEGIN { - $__PARAMETERMAP = @{} - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } + $__PARAMETERMAP = @{} + $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } } PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += 'settings' - $__commandArgs += 'export' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS + $__boundParameters = $PSBoundParameters + $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name + $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) + $__commandArgs = @() + $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) + if ($__boundParameters["Debug"]){wait-debugger} + $__commandArgs += 'settings' + $__commandArgs += 'export' + foreach ($paramName in $__boundParameters.Keys| + Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| + Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { + $value = $__boundParameters[$paramName] + $param = $__PARAMETERMAP[$paramName] + if ($param) { + if ($value -is [switch]) { + if ($value.IsPresent) { + if ($param.OriginalName) { $__commandArgs += $param.OriginalName } + } + elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } + } + elseif ( $param.NoGap ) { + $pFmt = "{0}{1}" + if($value -match "\s") { $pFmt = "{0}""{1}""" } + $__commandArgs += $pFmt -f $param.OriginalName, $value + } + else { + if($param.OriginalName) { $__commandArgs += $param.OriginalName } + $__commandArgs += $value | Foreach-Object {$_} + } + } + } + $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} + if ($__boundParameters["Debug"]){wait-debugger} + if ( $__boundParameters["Verbose"]) { + Write-Verbose -Verbose -Message winget.exe + $__commandArgs | Write-Verbose -Verbose + } + $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] + if (! $__handlerInfo ) { + $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present + } + $__handler = $__handlerInfo.Handler + if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { + # check for the application and throw if it cannot be found + if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { + throw "Cannot find executable 'winget.exe'" + } + if ( $__handlerInfo.StreamOutput ) { + & "winget.exe" $__commandArgs | & $__handler + } + else { + $result = & "winget.exe" $__commandArgs + & $__handler $result + } + } +} # end PROCESS } <# - .SYNOPSIS - Add a new source. +.SYNOPSIS +Add a new source. - .DESCRIPTION - Add a new source. A source provides the data for you to discover and install packages. - Only add a new source if you trust it as a secure location. +.DESCRIPTION +Add a new source. A source provides the data for you to discover and install packages. +Only add a new source if you trust it as a secure location. - .PARAMETER Name - Name of the source. +.PARAMETER Name +Name of the source. - .PARAMETER Argument - Argument to be given to the source. +.PARAMETER Argument +Argument to be given to the source. - .PARAMETER Type - Type of the source. +.PARAMETER Type +Type of the source. - .INPUTS - None. +.INPUTS +None. - .OUTPUTS - None. +.OUTPUTS +None. - .EXAMPLE - PS> Add-WinGetSource -Name Contoso -Argument https://www.contoso.com/cache +.EXAMPLE +PS> Add-WinGetSource -Name Contoso -Argument https://www.contoso.com/cache #> function Add-WinGetSource @@ -440,116 +354,116 @@ param( [string]$Argument, [Parameter(Position=2,ValueFromPipelineByPropertyName=$true)] [string]$Type - ) + ) BEGIN { - $__PARAMETERMAP = @{ - Name = @{ - OriginalName = '--name' - OriginalPosition = '0' - Position = '0' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - Argument = @{ - OriginalName = '--arg' - OriginalPosition = '0' - Position = '1' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - Type = @{ - OriginalName = '--type' - OriginalPosition = '0' - Position = '2' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - } - - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } + $__PARAMETERMAP = @{ + Name = @{ + OriginalName = '--name' + OriginalPosition = '0' + Position = '0' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + Argument = @{ + OriginalName = '--arg' + OriginalPosition = '0' + Position = '1' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + Type = @{ + OriginalName = '--type' + OriginalPosition = '0' + Position = '2' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + } + + $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } } PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += 'source' - $__commandArgs += 'add' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS + $__boundParameters = $PSBoundParameters + $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name + $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) + $__commandArgs = @() + $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) + if ($__boundParameters["Debug"]){wait-debugger} + $__commandArgs += 'source' + $__commandArgs += 'add' + foreach ($paramName in $__boundParameters.Keys| + Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| + Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { + $value = $__boundParameters[$paramName] + $param = $__PARAMETERMAP[$paramName] + if ($param) { + if ($value -is [switch]) { + if ($value.IsPresent) { + if ($param.OriginalName) { $__commandArgs += $param.OriginalName } + } + elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } + } + elseif ( $param.NoGap ) { + $pFmt = "{0}{1}" + if($value -match "\s") { $pFmt = "{0}""{1}""" } + $__commandArgs += $pFmt -f $param.OriginalName, $value + } + else { + if($param.OriginalName) { $__commandArgs += $param.OriginalName } + $__commandArgs += $value | Foreach-Object {$_} + } + } + } + $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} + if ($__boundParameters["Debug"]){wait-debugger} + if ( $__boundParameters["Verbose"]) { + Write-Verbose -Verbose -Message winget.exe + $__commandArgs | Write-Verbose -Verbose + } + $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] + if (! $__handlerInfo ) { + $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present + } + $__handler = $__handlerInfo.Handler + if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { + # check for the application and throw if it cannot be found + if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { + throw "Cannot find executable 'winget.exe'" + } + if ( $__handlerInfo.StreamOutput ) { + & "winget.exe" $__commandArgs | & $__handler + } + else { + $result = & "winget.exe" $__commandArgs + & $__handler $result + } + } +} # end PROCESS } <# - .SYNOPSIS - Remove a specific source. +.SYNOPSIS +Remove a specific source. - .DESCRIPTION - Remove a specific source. The source must already exist to be removed. +.DESCRIPTION +Remove a specific source. The source must already exist to be removed. - .PARAMETER Name - Name of the source. +.PARAMETER Name +Name of the source. - .INPUTS - None. +.INPUTS +None. - .OUTPUTS - None. +.OUTPUTS +None. - .EXAMPLE - PS> Remove-WinGetSource -Name Contoso +.EXAMPLE +PS> Remove-WinGetSource -Name Contoso #> function Remove-WinGetSource @@ -560,104 +474,104 @@ function Remove-WinGetSource param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [string]$Name - ) + ) BEGIN { - $__PARAMETERMAP = @{ - Name = @{ - OriginalName = '--name' - OriginalPosition = '0' - Position = '0' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - } - - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } + $__PARAMETERMAP = @{ + Name = @{ + OriginalName = '--name' + OriginalPosition = '0' + Position = '0' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + } + + $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } } PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += 'source' - $__commandArgs += 'remove' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS + $__boundParameters = $PSBoundParameters + $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name + $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) + $__commandArgs = @() + $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) + if ($__boundParameters["Debug"]){wait-debugger} + $__commandArgs += 'source' + $__commandArgs += 'remove' + foreach ($paramName in $__boundParameters.Keys| + Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| + Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { + $value = $__boundParameters[$paramName] + $param = $__PARAMETERMAP[$paramName] + if ($param) { + if ($value -is [switch]) { + if ($value.IsPresent) { + if ($param.OriginalName) { $__commandArgs += $param.OriginalName } + } + elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } + } + elseif ( $param.NoGap ) { + $pFmt = "{0}{1}" + if($value -match "\s") { $pFmt = "{0}""{1}""" } + $__commandArgs += $pFmt -f $param.OriginalName, $value + } + else { + if($param.OriginalName) { $__commandArgs += $param.OriginalName } + $__commandArgs += $value | Foreach-Object {$_} + } + } + } + $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} + if ($__boundParameters["Debug"]){wait-debugger} + if ( $__boundParameters["Verbose"]) { + Write-Verbose -Verbose -Message winget.exe + $__commandArgs | Write-Verbose -Verbose + } + $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] + if (! $__handlerInfo ) { + $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present + } + $__handler = $__handlerInfo.Handler + if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { + # check for the application and throw if it cannot be found + if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { + throw "Cannot find executable 'winget.exe'" + } + if ( $__handlerInfo.StreamOutput ) { + & "winget.exe" $__commandArgs | & $__handler + } + else { + $result = & "winget.exe" $__commandArgs + & $__handler $result + } + } +} # end PROCESS } <# - .SYNOPSIS - Drops existing sources. Without any argument, this command will drop all sources and add the defaults. +.SYNOPSIS +Drops existing sources. Without any argument, this command will drop all sources and add the defaults. - .DESCRIPTION - Drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. - If a named source is provided, only that source will be dropped. +.DESCRIPTION +Drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. +If a named source is provided, only that source will be dropped. - .PARAMETER Name - Name of the source. +.PARAMETER Name +Name of the source. - .INPUTS - None. +.INPUTS +None. - .OUTPUTS - None. +.OUTPUTS +None. - .EXAMPLE - PS> Reset-WinGetSource +.EXAMPLE +PS> Reset-WinGetSource - .EXAMPLE - PS> Reset-WinGetSource -Name Contoso +.EXAMPLE +PS> Reset-WinGetSource -Name Contoso #> function Reset-WinGetSource @@ -668,81 +582,80 @@ function Reset-WinGetSource param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [string]$Name - ) + ) BEGIN { - $__PARAMETERMAP = @{ - Name = @{ - OriginalName = '--name' - OriginalPosition = '0' - Position = '0' - ParameterType = 'string' - ApplyToExecutable = $False - NoGap = $False - } - } - - $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } + $__PARAMETERMAP = @{ + Name = @{ + OriginalName = '--name' + OriginalPosition = '0' + Position = '0' + ParameterType = 'string' + ApplyToExecutable = $False + NoGap = $False + } + } + + $__outputHandlers = @{ Default = @{ StreamOutput = $true; Handler = { $input } } } } PROCESS { - $__boundParameters = $PSBoundParameters - $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name - $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) - $__commandArgs = @() - $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) - if ($__boundParameters["Debug"]){wait-debugger} - $__commandArgs += 'source' - $__commandArgs += 'reset' - $__commandArgs += '--force' - foreach ($paramName in $__boundParameters.Keys| - Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| - Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { - $value = $__boundParameters[$paramName] - $param = $__PARAMETERMAP[$paramName] - if ($param) { - if ($value -is [switch]) { - if ($value.IsPresent) { - if ($param.OriginalName) { $__commandArgs += $param.OriginalName } - } - elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } - } - elseif ( $param.NoGap ) { - $pFmt = "{0}{1}" - if($value -match "\s") { $pFmt = "{0}""{1}""" } - $__commandArgs += $pFmt -f $param.OriginalName, $value - } - else { - if($param.OriginalName) { $__commandArgs += $param.OriginalName } - $__commandArgs += $value | Foreach-Object {$_} - } - } - } - $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} - if ($__boundParameters["Debug"]){wait-debugger} - if ( $__boundParameters["Verbose"]) { - Write-Verbose -Verbose -Message winget.exe - $__commandArgs | Write-Verbose -Verbose - } - $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] - if (! $__handlerInfo ) { - $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present - } - $__handler = $__handlerInfo.Handler - if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { - # check for the application and throw if it cannot be found - if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { - throw "Cannot find executable 'winget.exe'" - } - if ( $__handlerInfo.StreamOutput ) { - & "winget.exe" $__commandArgs | & $__handler - } - else { - $result = & "winget.exe" $__commandArgs - & $__handler $result - } - } - } # end PROCESS + $__boundParameters = $PSBoundParameters + $__defaultValueParameters = $PSCmdlet.MyInvocation.MyCommand.Parameters.Values.Where({$_.Attributes.Where({$_.TypeId.Name -eq "PSDefaultValueAttribute"})}).Name + $__defaultValueParameters.Where({ !$__boundParameters["$_"] }).ForEach({$__boundParameters["$_"] = get-variable -value $_}) + $__commandArgs = @() + $MyInvocation.MyCommand.Parameters.Values.Where({$_.SwitchParameter -and $_.Name -notmatch "Debug|Whatif|Confirm|Verbose" -and ! $__boundParameters[$_.Name]}).ForEach({$__boundParameters[$_.Name] = [switch]::new($false)}) + if ($__boundParameters["Debug"]){wait-debugger} + $__commandArgs += 'source' + $__commandArgs += 'reset' + $__commandArgs += '--force' + foreach ($paramName in $__boundParameters.Keys| + Where-Object {!$__PARAMETERMAP[$_].ApplyToExecutable}| + Sort-Object {$__PARAMETERMAP[$_].OriginalPosition}) { + $value = $__boundParameters[$paramName] + $param = $__PARAMETERMAP[$paramName] + if ($param) { + if ($value -is [switch]) { + if ($value.IsPresent) { + if ($param.OriginalName) { $__commandArgs += $param.OriginalName } + } + elseif ($param.DefaultMissingValue) { $__commandArgs += $param.DefaultMissingValue } + } + elseif ( $param.NoGap ) { + $pFmt = "{0}{1}" + if($value -match "\s") { $pFmt = "{0}""{1}""" } + $__commandArgs += $pFmt -f $param.OriginalName, $value + } + else { + if($param.OriginalName) { $__commandArgs += $param.OriginalName } + $__commandArgs += $value | Foreach-Object {$_} + } + } + } + $__commandArgs = $__commandArgs | Where-Object {$_ -ne $null} + if ($__boundParameters["Debug"]){wait-debugger} + if ( $__boundParameters["Verbose"]) { + Write-Verbose -Verbose -Message winget.exe + $__commandArgs | Write-Verbose -Verbose + } + $__handlerInfo = $__outputHandlers[$PSCmdlet.ParameterSetName] + if (! $__handlerInfo ) { + $__handlerInfo = $__outputHandlers["Default"] # Guaranteed to be present + } + $__handler = $__handlerInfo.Handler + if ( $PSCmdlet.ShouldProcess("winget.exe $__commandArgs")) { + # check for the application and throw if it cannot be found + if ( -not (Get-Command -ErrorAction Ignore "winget.exe")) { + throw "Cannot find executable 'winget.exe'" + } + if ( $__handlerInfo.StreamOutput ) { + & "winget.exe" $__commandArgs | & $__handler + } + else { + $result = & "winget.exe" $__commandArgs + & $__handler $result + } + } +} # end PROCESS } - diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs index 8068aba430..0da3a72524 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs @@ -87,6 +87,78 @@ internal static string FindPackagesExceptionMessage { } } + /// + /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled.. + /// + internal static string IntegrityAppExecutionAliasDisabledMessage { + get { + return ResourceManager.GetString("IntegrityAppExecutionAliasDisabledMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer is not installed.. + /// + internal static string IntegrityAppInstallerNotInstalledMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotInstalledMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer is not registered.. + /// + internal static string IntegrityAppInstallerNotRegisteredMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotRegisteredMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer does not contain the Windows Package Manager.. + /// + internal static string IntegrityAppInstallerNotSupportedMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotSupportedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows Package Manager returned an unexcepted result.. + /// + internal static string IntegrityFailureMessage { + get { + return ResourceManager.GetString("IntegrityFailureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer did not automatically add the PATH environment variable.. + /// + internal static string IntegrityNotInPathMessage { + get { + return ResourceManager.GetString("IntegrityNotInPathMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Windows Package Manager requires at least Version 1809 (October 2018 Update).. + /// + internal static string IntegrityOsNotSupportedMessage { + get { + return ResourceManager.GetString("IntegrityOsNotSupportedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to execute winget command.. + /// + internal static string IntegrityUnknownMessage { + get { + return ResourceManager.GetString("IntegrityUnknownMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to No source matches the given value: {0}. /// @@ -169,11 +241,11 @@ internal static string WinGetCLIExceptionMessage { } /// - /// Looks up a localized string similar to Unable to execute command; WinGet package not installed.. + /// Looks up a localized string similar to Winget command run timed out: {0} {1}. /// - internal static string WinGetPackageNotInstalledMessage { + internal static string WinGetCLITimeoutExceptionMessage { get { - return ResourceManager.GetString("WinGetPackageNotInstalledMessage", resourceCulture); + return ResourceManager.GetString("WinGetCLITimeoutExceptionMessage", resourceCulture); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx index 89abdcce96..34a139148a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx @@ -154,8 +154,8 @@ {0}, {1}, and {2} other packages matched the input criteria. Please refine the input. {0} - The first conflicting package as a string. {1} - The second conflicting package. {2} - The number of other packages that also matched the input criteria. - - Unable to execute command; WinGet package not installed. + + Unable to execute winget command. Command {0} failed with exit code {1} @@ -163,4 +163,28 @@ User settings file is invalid. + + The App Execution Alias for the Windows Package Manager is disabled. + + + The App Installer is not installed. + + + The App Installer does not contain the Windows Package Manager. + + + Windows Package Manager returned an unexcepted result. + + + The App Installer did not automatically add the PATH environment variable. + + + The Windows Package Manager requires at least Version 1809 (October 2018 Update). + + + Winget command run timed out: {0} {1} + + + The App Installer is not registered. + \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 index 0e1a23842c..4c6d69a36e 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 @@ -304,4 +304,51 @@ class WinGetSourcesResource } } +[DSCResource()] +class WinGetIntegrityResource +{ + # We need a key. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty()] + [string]$Version = "" + + [DscProperty()] + [bool]$UsePrerelease = $false + + # There's no much value on this. + [WinGetIntegrityResource] Get() + { + # TODO: add version via Get-WinGetVersion + $result = @{ + SID = '' + } + return $result + } + + # Tests winget is installed. + [bool] Test() + { + Assert-WinGetCommand "Assert-WinGetIntegrity" + + try + { + Assert-WinGetIntegrity + } + catch + { + return $false + } + + return $true + } + + # Repairs Winget. + [void] Set() + { + + } +} + #endregion DscResources From 80f50e41454ea204ab33f63a87b961bd90e982fa Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 10 Jan 2023 16:27:15 -0800 Subject: [PATCH 02/11] Impor appx helper --- .../Helpers/AppxModuleHelper.cs | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs index 4085ec44d9..74b77d7f03 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -6,7 +6,6 @@ namespace Microsoft.WinGet.Client.Helpers { - using System; using System.IO; using System.Linq; using System.Management.Automation; @@ -16,23 +15,23 @@ namespace Microsoft.WinGet.Client.Helpers /// /// Helper to make calls to the Appx module. - /// There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without - /// the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between - /// using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session. - /// https://github.com/PowerShell/PowerShell/issues/13138. /// internal class AppxModuleHelper { + private const string GetAppxModule = "Get-Module Appx"; private const string ImportModuleCore = "Import-Module Appx -UseWindowsPowerShell"; private const string GetAppxPackageCommand = "Get-AppxPackage {0}"; private const string AddAppxPackageFormat = "Add-AppxPackage -Path {0}"; - private const string AppxManifest = "AppxManifest.xml"; - + private const string AddAppxPackageRegisterFormat = "Add-AppxPackage -Path {0} -Register -DisableDevelopmentMode"; private const string ForceUpdateFromAnyVersion = " -ForceUpdateFromAnyVersion"; - private const string Register = "-Register"; - private const string DisableDevelopmentMode = "-DisableDevelopmentMode"; private const string AppInstallerName = "Microsoft.DesktopAppInstaller"; + private const string AppxManifest = "AppxManifest.xml"; + private const string PackageFullName = "PackageFullName"; + + // Dependencies + private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; + private const string UiXaml27 = "Microsoft.UI.Xaml.2.7"; private readonly CommandInvocationIntrinsics commandInvocation; @@ -44,8 +43,16 @@ public AppxModuleHelper(CommandInvocationIntrinsics commandInvocation) { this.commandInvocation = commandInvocation; + // There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without + // the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between + // using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session. + // https://github.com/PowerShell/PowerShell/issues/13138. #if !POWERSHELL_WINDOWS - this.commandInvocation.InvokeScript(ImportModuleCore); + var appxModule = this.commandInvocation.InvokeScript(GetAppxModule); + if (appxModule is null) + { + this.commandInvocation.InvokeScript(ImportModuleCore); + } #endif } @@ -80,15 +87,14 @@ public string GetAppInstallerPropertyValue(string propertyName) } /// - /// Calls Add-AppxPackage with the path specified. + /// Calls Add-AppxPackage with the specified path. /// /// The path of the package to add. /// If the package version is lower than the installed one. public void AddAppInstallerBundle(string localPath, bool downgrade = false) { - // TODO: We need to follow up for Microsoft.UI.Xaml.2.7 - // downloading the nuget and extracting it doesn't sound like the right thing to do. this.InstallVCLibsDependencies(); + this.InstallUiXaml(); StringBuilder sb = new StringBuilder(); sb.Append(string.Format(AddAppxPackageFormat, localPath)); @@ -102,18 +108,18 @@ public void AddAppInstallerBundle(string localPath, bool downgrade = false) } /// - /// Calls Add-AppxPackage to register. + /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml. /// public void RegisterAppInstaller() { - string packageFullName = this.GetAppInstallerPropertyValue("PackageFullName"); + string packageFullName = this.GetAppInstallerPropertyValue(PackageFullName); string appxManifestPath = Path.Combine( Utilities.ProgramFilesWindowsAppPath, packageFullName, AppxManifest); this.commandInvocation.InvokeScript( - $"{string.Format(AddAppxPackageFormat, appxManifestPath)} {Register} {DisableDevelopmentMode}"); + string.Format(AddAppxPackageRegisterFormat, appxManifestPath)); } private PSObject GetAppxObject(string packageName) @@ -125,8 +131,8 @@ private PSObject GetAppxObject(string packageName) private void InstallVCLibsDependencies() { - var vcLibsPackageObj = this.GetAppxObject("Microsoft.VCLibs.140.00.UWPDesktop_8wekyb3d8bbwe"); - if (vcLibsPackageObj is null) + var vcLibsPackageObjs = this.GetAppxObject(VCLibsUWPDesktop); + if (vcLibsPackageObjs is null) { var arch = RuntimeInformation.OSArchitecture; string url; @@ -152,5 +158,16 @@ private void InstallVCLibsDependencies() this.commandInvocation.InvokeScript(string.Format(AddAppxPackageFormat, tmpFile)); } } + + private void InstallUiXaml() + { + // TODO: We need to follow up for Microsoft.UI.Xaml.2.7 + // downloading the nuget and extracting it doesn't sound like the right thing to do. + var uiXamlObjs = this.GetAppxObject(UiXaml27); + if (uiXamlObjs is null) + { + throw new PSNotImplementedException("TODO: message"); + } + } } } From f440c8f2699716c18aed75499e2e40ab67b08a29 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 10 Jan 2023 19:41:47 -0800 Subject: [PATCH 03/11] Add debug messages --- .../Commands/AssertIntegrityCommand.cs | 2 +- .../Commands/GetVersionCommand.cs | 1 + .../Commands/RepairCommand.cs | 155 +++++++++++------- .../Common/RepairResult.cs | 54 ++++++ .../Common/WinGetIntegrity.cs | 16 +- .../Helpers/AppxModuleHelper.cs | 39 +++-- .../Properties/Resources.Designer.cs | 27 +++ .../Properties/Resources.resx | 9 + 8 files changed, 224 insertions(+), 79 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs index 6e7b28cee9..7493c92b03 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs @@ -22,7 +22,7 @@ public class AssertIntegrityCommand : BaseCommand /// protected override void ProcessRecord() { - WinGetIntegrity.AssertWinGet(this.InvokeCommand); + WinGetIntegrity.AssertWinGet(this); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs index 68306aafb7..f91377d4cd 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs @@ -15,6 +15,7 @@ namespace Microsoft.WinGet.Client.Commands /// Get-WinGetVersion. Gets the current version of winget. /// [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] + [OutputType(typeof(string))] public class GetVersionCommand : BaseCommand { /// diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs index 5631912d7a..b0afd4daaf 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -11,6 +11,7 @@ namespace Microsoft.WinGet.Client.Commands using Microsoft.WinGet.Client.Commands.Common; using Microsoft.WinGet.Client.Common; using Microsoft.WinGet.Client.Helpers; + using Microsoft.WinGet.Client.Properties; /// /// Repair-WinGet. Repairs winget if needed. @@ -19,6 +20,7 @@ namespace Microsoft.WinGet.Client.Commands public class RepairCommand : BaseCommand { private const string EnvPath = "env:PATH"; + private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; /// /// Gets or sets the optional version. @@ -38,88 +40,127 @@ public class RepairCommand : BaseCommand /// protected override void ProcessRecord() { - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this.InvokeCommand); + RepairResult repairResult = RepairResult.Failure; + + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this); + + this.WriteDebug($"Integrity category type: {integrityCategory}"); bool preRelease = this.IncludePreRelease.ToBool(); if (integrityCategory == IntegrityCategory.Installed) { - string toInstallVersion = this.Version; - var gitHubRelease = new GitHubRelease(); - - if (string.IsNullOrEmpty(toInstallVersion)) - { - toInstallVersion = gitHubRelease.GetLatestVersionTagName(preRelease); - } - - if (toInstallVersion != WinGetVersionHelper.InstalledWinGetVersion) - { - this.WriteObject($"Current installed version {WinGetVersionHelper.InstalledWinGetVersion}"); - this.WriteObject($"Version to install {toInstallVersion}"); - - var downloadedMsixBundlePath = gitHubRelease.DownloadRelease( - preRelease, - toInstallVersion); - - var installedVersion = WinGetVersionHelper.ConvertInstalledWinGetVersion(); - var inputVersion = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); - - bool downgrade = false; - if (installedVersion.CompareTo(inputVersion) > 0) - { - downgrade = true; - } - - var appxModule = new AppxModuleHelper(this.InvokeCommand); - appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); - } - else - { - this.WriteObject("WinGet is in good state"); - } + repairResult = this.RepairForInstalled(preRelease, this.Version); } else if (integrityCategory == IntegrityCategory.NotInPath) { - // 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.RepairEnvPath(); + repairResult = RepairResult.PathUpdated; } else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) { - var appxModule = new AppxModuleHelper(this.InvokeCommand); + var appxModule = new AppxModuleHelper(this); appxModule.RegisterAppInstaller(); + + this.WriteDebug($"WinGet version {WinGetVersionHelper.InstalledWinGetVersion} registered"); + repairResult = RepairResult.Registered; } else if (integrityCategory == IntegrityCategory.AppInstallerNotInstalled || integrityCategory == IntegrityCategory.AppInstallerNotSupported || integrityCategory == IntegrityCategory.Failure) { - // Download and install. - var gitHubRelease = new GitHubRelease(); - var downloadedMsixBundlePath = gitHubRelease.DownloadRelease( - this.IncludePreRelease.ToBool(), - this.Version); - - var appxModule = new AppxModuleHelper(this.InvokeCommand); - appxModule.AddAppInstallerBundle(downloadedMsixBundlePath); - - this.WriteObject($"Version to install {WinGetVersionHelper.InstalledWinGetVersion}"); + if (this.DownloadAndInstall(preRelease, this.Version, false)) + { + repairResult = RepairResult.Installed; + } } else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) { // Sorry, but the user has to manually enabled it. - throw new Exception("app installer"); + this.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); + repairResult = RepairResult.NeedsManualRepair; + } + else + { + this.WriteInformation(Resources.WinGetNotSupportedMessage, WriteInformationTags); + } + + this.WriteObject(repairResult); + } + + private RepairResult RepairForInstalled(bool preRelease, string toInstallVersion) + { + RepairResult repairResult = RepairResult.Failure; + + if (string.IsNullOrEmpty(toInstallVersion)) + { + var gitHubRelease = new GitHubRelease(); + toInstallVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + } + + if (toInstallVersion != WinGetVersionHelper.InstalledWinGetVersion) + { + this.WriteDebug($"Installed WinGet version {WinGetVersionHelper.InstalledWinGetVersion}"); + this.WriteDebug($"Installing WinGet version {toInstallVersion}"); + + // TODO: write debug, current version installed and version to install. + var installedVersion = WinGetVersionHelper.ConvertInstalledWinGetVersion(); + var inputVersion = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); + + bool downgrade = false; + if (installedVersion.CompareTo(inputVersion) > 0) + { + downgrade = true; + } + + if (this.DownloadAndInstall(preRelease, toInstallVersion, downgrade)) + { + repairResult = downgrade ? RepairResult.Downgraded : RepairResult.Updated; + } } else { - // Unknown - // OsNotSupported - throw new Exception("impossible"); + this.WriteDebug($"Installed WinGet version and target match {WinGetVersionHelper.InstalledWinGetVersion}"); + repairResult = RepairResult.Noop; + } + + return repairResult; + } + + private bool DownloadAndInstall(bool preRelease, string versionTag, bool downgrade) + { + // Download and install. + var gitHubRelease = new GitHubRelease(); + var downloadedMsixBundlePath = gitHubRelease.DownloadRelease( + preRelease, + versionTag); + + var appxModule = new AppxModuleHelper(this); + appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); + + // Verify that is installed + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this); + if (integrityCategory != IntegrityCategory.Installed) + { + return false; } + + this.WriteDebug($"Installed WinGet version {WinGetVersionHelper.InstalledWinGetVersion}"); + 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"); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs new file mode 100644 index 0000000000..efce0cccf8 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Common +{ + /// + /// Repair result. + /// + public enum RepairResult + { + /// + /// Failure. + /// + Failure, + + /// + /// No changes were made. + /// + Noop, + + /// + /// WinGet installed. + /// + Installed, + + /// + /// WinGet registered. + /// + Registered, + + /// + /// WinGet updated. + /// + Updated, + + /// + /// WinGet downgraded. + /// + Downgraded, + + /// + /// Path environment fixed. + /// + PathUpdated, + + /// + /// NeedsManual repair. + /// + NeedsManualRepair, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs index 89af4d1c4d..79eb5552b0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -22,8 +22,8 @@ internal static class WinGetIntegrity /// /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. /// - /// CommandInvocationIntrinsics of the calling cmdlet. - public static void AssertWinGet(CommandInvocationIntrinsics commandInvocation) + /// The calling cmdlet. + public static void AssertWinGet(PSCmdlet pSCmdlet) { try { @@ -35,7 +35,7 @@ public static void AssertWinGet(CommandInvocationIntrinsics commandInvocation) } catch (Win32Exception) { - throw new WinGetIntegrityException(GetReason(commandInvocation)); + throw new WinGetIntegrityException(GetReason(pSCmdlet)); } catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) { @@ -50,13 +50,13 @@ public static void AssertWinGet(CommandInvocationIntrinsics commandInvocation) /// /// Verifies winget runs correctly. /// - /// CommandInvocationIntrinsics of the calling cmdlet. + /// The calling cmdlet. /// Integrity category. - public static IntegrityCategory GetIntegrityCategory(CommandInvocationIntrinsics commandInvocation) + public static IntegrityCategory GetIntegrityCategory(PSCmdlet pSCmdlet) { try { - AssertWinGet(commandInvocation); + AssertWinGet(pSCmdlet); } catch (WinGetIntegrityException e) { @@ -66,7 +66,7 @@ public static IntegrityCategory GetIntegrityCategory(CommandInvocationIntrinsics return IntegrityCategory.Installed; } - private static IntegrityCategory GetReason(CommandInvocationIntrinsics commandInvocation) + private static IntegrityCategory GetReason(PSCmdlet pSCmdlet) { // Ok, so you are here because calling winget --version failed. Lets try to figure out why. @@ -105,7 +105,7 @@ private static IntegrityCategory GetReason(CommandInvocationIntrinsics commandIn // It could be that AppInstaller package is old or the package is not // registered at this point. To know that, call Get-AppxPackage. - var appxModule = new AppxModuleHelper(commandInvocation); + var appxModule = new AppxModuleHelper(pSCmdlet); string version = appxModule.GetAppInstallerPropertyValue("Version"); if (version is null) { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs index 74b77d7f03..892aa0ba71 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -9,9 +9,11 @@ namespace Microsoft.WinGet.Client.Helpers using System.IO; using System.Linq; using System.Management.Automation; + using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; using System.Text; using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Properties; /// /// Helper to make calls to the Appx module. @@ -24,6 +26,7 @@ internal class AppxModuleHelper private const string AddAppxPackageFormat = "Add-AppxPackage -Path {0}"; private const string AddAppxPackageRegisterFormat = "Add-AppxPackage -Path {0} -Register -DisableDevelopmentMode"; private const string ForceUpdateFromAnyVersion = " -ForceUpdateFromAnyVersion"; + private const string GetAppxPackageByVersionCommand = "Get-AppxPackage {0} | Where-Object -Property Version -eq {1}"; private const string AppInstallerName = "Microsoft.DesktopAppInstaller"; private const string AppxManifest = "AppxManifest.xml"; @@ -31,27 +34,28 @@ internal class AppxModuleHelper // Dependencies private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; + private const string VCLibsUWPDesktopVersion = "14.0.30704.0"; private const string UiXaml27 = "Microsoft.UI.Xaml.2.7"; - private readonly CommandInvocationIntrinsics commandInvocation; + private readonly PSCmdlet psCmdlet; /// /// Initializes a new instance of the class. /// - /// From calling cmdlet. - public AppxModuleHelper(CommandInvocationIntrinsics commandInvocation) + /// The calling cmdlet. + public AppxModuleHelper(PSCmdlet psCmdlet) { - this.commandInvocation = commandInvocation; + this.psCmdlet = psCmdlet; // There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without // the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between // using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session. // https://github.com/PowerShell/PowerShell/issues/13138. #if !POWERSHELL_WINDOWS - var appxModule = this.commandInvocation.InvokeScript(GetAppxModule); + var appxModule = this.psCmdlet.InvokeCommand.InvokeScript(GetAppxModule); if (appxModule is null) { - this.commandInvocation.InvokeScript(ImportModuleCore); + this.psCmdlet.InvokeCommand.InvokeScript(ImportModuleCore); } #endif } @@ -104,7 +108,13 @@ public void AddAppInstallerBundle(string localPath, bool downgrade = false) sb.Append(ForceUpdateFromAnyVersion); } - this.commandInvocation.InvokeScript(sb.ToString()); + // Using this method simplifies a lot of things, but the error is not propagated with + // the default parameters. PipelineResultTypes.Error will at least output it in the terminal. + this.psCmdlet.InvokeCommand.InvokeScript( + sb.ToString(), + useNewScope: true, + PipelineResultTypes.Error, + input: null); } /// @@ -118,21 +128,23 @@ public void RegisterAppInstaller() packageFullName, AppxManifest); - this.commandInvocation.InvokeScript( + this.psCmdlet.InvokeCommand.InvokeScript( string.Format(AddAppxPackageRegisterFormat, appxManifestPath)); } private PSObject GetAppxObject(string packageName) { - return this.commandInvocation + return this.psCmdlet.InvokeCommand .InvokeScript(string.Format(GetAppxPackageCommand, packageName)) .FirstOrDefault(); } private void InstallVCLibsDependencies() { - var vcLibsPackageObjs = this.GetAppxObject(VCLibsUWPDesktop); - if (vcLibsPackageObjs is null) + var vcLibsPackageObjs = this.psCmdlet.InvokeCommand + .InvokeScript(string.Format(GetAppxPackageByVersionCommand, VCLibsUWPDesktop, VCLibsUWPDesktopVersion)); + if (vcLibsPackageObjs is null || + vcLibsPackageObjs.Count == 0) { var arch = RuntimeInformation.OSArchitecture; string url; @@ -155,7 +167,8 @@ private void InstallVCLibsDependencies() var githubRelease = new GitHubRelease(); githubRelease.DownloadUrl(url, tmpFile); - this.commandInvocation.InvokeScript(string.Format(AddAppxPackageFormat, tmpFile)); + this.psCmdlet.WriteDebug($"Installing VCLibs {url}"); + this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, tmpFile)); } } @@ -166,7 +179,7 @@ private void InstallUiXaml() var uiXamlObjs = this.GetAppxObject(UiXaml27); if (uiXamlObjs is null) { - throw new PSNotImplementedException("TODO: message"); + throw new PSNotImplementedException(Resources.MicrosoftUIXaml27Message); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs index 0da3a72524..ff791973e5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it.. + /// + internal static string AppExecutionAliasDisabledHelpMessage { + get { + return ResourceManager.GetString("AppExecutionAliasDisabledHelpMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to An error occurred while connecting to the catalog.. /// @@ -177,6 +186,15 @@ internal static string InvalidVersionExceptionMessage { } } + /// + /// Looks up a localized string similar to Microsoft.UI.Xaml.2.7 package is not installed. + /// + internal static string MicrosoftUIXaml27Message { + get { + return ResourceManager.GetString("MicrosoftUIXaml27Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to No packages matched the given input criteria.. /// @@ -248,5 +266,14 @@ internal static string WinGetCLITimeoutExceptionMessage { return ResourceManager.GetString("WinGetCLITimeoutExceptionMessage", resourceCulture); } } + + /// + /// Looks up a localized string similar to Windows Package Manager not supported.. + /// + internal static string WinGetNotSupportedMessage { + get { + return ResourceManager.GetString("WinGetNotSupportedMessage", resourceCulture); + } + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx index 34a139148a..4973718636 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx @@ -187,4 +187,13 @@ The App Installer is not registered. + + The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it. + + + Microsoft.UI.Xaml.2.7 package is not installed + + + Windows Package Manager not supported. + \ No newline at end of file From 54dfddb306975ad2d2dba4195df8407753f4c74d Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 10 Jan 2023 19:50:09 -0800 Subject: [PATCH 04/11] Remove TODOs --- .../Commands/RepairCommand.cs | 1 - .../Microsoft.WinGet.DSC.psm1 | 47 ------------------- 2 files changed, 48 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs index b0afd4daaf..99caa04033 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -103,7 +103,6 @@ private RepairResult RepairForInstalled(bool preRelease, string toInstallVersion this.WriteDebug($"Installed WinGet version {WinGetVersionHelper.InstalledWinGetVersion}"); this.WriteDebug($"Installing WinGet version {toInstallVersion}"); - // TODO: write debug, current version installed and version to install. var installedVersion = WinGetVersionHelper.ConvertInstalledWinGetVersion(); var inputVersion = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 index 4c6d69a36e..0e1a23842c 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 @@ -304,51 +304,4 @@ class WinGetSourcesResource } } -[DSCResource()] -class WinGetIntegrityResource -{ - # We need a key. Do not set. - [DscProperty(Key)] - [string]$SID - - [DscProperty()] - [string]$Version = "" - - [DscProperty()] - [bool]$UsePrerelease = $false - - # There's no much value on this. - [WinGetIntegrityResource] Get() - { - # TODO: add version via Get-WinGetVersion - $result = @{ - SID = '' - } - return $result - } - - # Tests winget is installed. - [bool] Test() - { - Assert-WinGetCommand "Assert-WinGetIntegrity" - - try - { - Assert-WinGetIntegrity - } - catch - { - return $false - } - - return $true - } - - # Repairs Winget. - [void] Set() - { - - } -} - #endregion DscResources From 5d77110313a4a0a401a6b56edb4853b604275d39 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 11 Jan 2023 13:26:38 -0800 Subject: [PATCH 05/11] Add version to assert-wingetintegrity --- .../Commands/AssertIntegrityCommand.cs | 8 ++++++- .../Commands/RepairCommand.cs | 23 +++++++++--------- .../Common/IntegrityCategory.cs | 5 ++++ .../Common/WinGetIntegrity.cs | 24 ++++++++++++++++--- .../Exceptions/WinGetIntegrityException.cs | 11 +++++++++ .../Helpers/WinGetVersionHelper.cs | 9 ------- .../Properties/Resources.Designer.cs | 9 +++++++ .../Properties/Resources.resx | 3 +++ 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs index 7493c92b03..c60e42bf9a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs @@ -16,13 +16,19 @@ namespace Microsoft.WinGet.Client.Commands [Cmdlet(VerbsLifecycle.Assert, Constants.WinGetNouns.Integrity)] public class AssertIntegrityCommand : BaseCommand { + /// + /// Gets or sets the optional version. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } = string.Empty; + /// /// Validates winget is installed correctly. If not, throws an exception /// with the reason why, if any. /// protected override void ProcessRecord() { - WinGetIntegrity.AssertWinGet(this); + WinGetIntegrity.AssertWinGet(this, this.Version); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs index 99caa04033..879ca7c4dd 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -42,15 +42,16 @@ protected override void ProcessRecord() { RepairResult repairResult = RepairResult.Failure; - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this); + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, this.Version); this.WriteDebug($"Integrity category type: {integrityCategory}"); bool preRelease = this.IncludePreRelease.ToBool(); - if (integrityCategory == IntegrityCategory.Installed) + if (integrityCategory == IntegrityCategory.Installed || + integrityCategory == IntegrityCategory.UnexpectedVersion) { - repairResult = this.RepairForInstalled(preRelease, this.Version); + repairResult = this.RepairForInstalled(preRelease, WinGetVersionHelper.InstalledWinGetVersion, this.Version); } else if (integrityCategory == IntegrityCategory.NotInPath) { @@ -88,7 +89,7 @@ protected override void ProcessRecord() this.WriteObject(repairResult); } - private RepairResult RepairForInstalled(bool preRelease, string toInstallVersion) + private RepairResult RepairForInstalled(bool preRelease, string installedVersion, string toInstallVersion) { RepairResult repairResult = RepairResult.Failure; @@ -98,16 +99,16 @@ private RepairResult RepairForInstalled(bool preRelease, string toInstallVersion toInstallVersion = gitHubRelease.GetLatestVersionTagName(preRelease); } - if (toInstallVersion != WinGetVersionHelper.InstalledWinGetVersion) + if (toInstallVersion != installedVersion) { - this.WriteDebug($"Installed WinGet version {WinGetVersionHelper.InstalledWinGetVersion}"); + this.WriteDebug($"Installed WinGet version {installedVersion}"); this.WriteDebug($"Installing WinGet version {toInstallVersion}"); - var installedVersion = WinGetVersionHelper.ConvertInstalledWinGetVersion(); - var inputVersion = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); + var v1 = WinGetVersionHelper.ConvertWinGetVersion(installedVersion); + var v2 = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); bool downgrade = false; - if (installedVersion.CompareTo(inputVersion) > 0) + if (v1.CompareTo(v2) > 0) { downgrade = true; } @@ -138,13 +139,13 @@ private bool DownloadAndInstall(bool preRelease, string versionTag, bool downgra appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); // Verify that is installed - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this); + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, versionTag); if (integrityCategory != IntegrityCategory.Installed) { return false; } - this.WriteDebug($"Installed WinGet version {WinGetVersionHelper.InstalledWinGetVersion}"); + this.WriteDebug($"Installed WinGet version {versionTag}"); return true; } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs index eb4733404b..b6ebbd136e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs @@ -16,6 +16,11 @@ public enum IntegrityCategory /// Installed, + /// + /// The version installed is not what is expected. + /// + UnexpectedVersion, + /// /// Unknown reason. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs index 79eb5552b0..c1f16ebc61 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -13,6 +13,7 @@ namespace Microsoft.WinGet.Client.Common using System.Management.Automation; using Microsoft.WinGet.Client.Exceptions; using Microsoft.WinGet.Client.Helpers; + using Microsoft.WinGet.Client.Properties; /// /// Validates winget runs correctly. @@ -23,7 +24,8 @@ internal static class WinGetIntegrity /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. /// /// The calling cmdlet. - public static void AssertWinGet(PSCmdlet pSCmdlet) + /// Expected version. + public static void AssertWinGet(PSCmdlet pSCmdlet, string expectedVersion) { try { @@ -32,6 +34,21 @@ public static void AssertWinGet(PSCmdlet pSCmdlet) var wingetCliWrapper = new WingetCLIWrapper(false); var result = wingetCliWrapper.RunCommand("--version"); result.VerifyExitCode(); + + if (!string.IsNullOrEmpty(expectedVersion)) + { + // Verify version + var installedVersion = WinGetVersionHelper.InstalledWinGetVersion; + if (expectedVersion != installedVersion) + { + throw new WinGetIntegrityException( + IntegrityCategory.UnexpectedVersion, + string.Format( + Resources.IntegrityUnexpectedVersionMessage, + installedVersion, + expectedVersion)); + } + } } catch (Win32Exception) { @@ -51,12 +68,13 @@ public static void AssertWinGet(PSCmdlet pSCmdlet) /// Verifies winget runs correctly. /// /// The calling cmdlet. + /// Expected version. /// Integrity category. - public static IntegrityCategory GetIntegrityCategory(PSCmdlet pSCmdlet) + public static IntegrityCategory GetIntegrityCategory(PSCmdlet pSCmdlet, string expectedVersion) { try { - AssertWinGet(pSCmdlet); + AssertWinGet(pSCmdlet, expectedVersion); } catch (WinGetIntegrityException e) { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs index 319540a911..489b1d1539 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetIntegrityException.cs @@ -36,6 +36,17 @@ public WinGetIntegrityException(IntegrityCategory category, Exception inner) this.Category = category; } + /// + /// Initializes a new instance of the class. + /// + /// Category failure. + /// Message. + public WinGetIntegrityException(IntegrityCategory category, string message) + : base(message) + { + this.Category = category; + } + /// /// Gets the category of the integrity failure. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs index 1acb6c2766..c07e9d1ab0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs @@ -26,15 +26,6 @@ public static string InstalledWinGetVersion } } - /// - /// Converts installed WinGet string version to a Version object. - /// - /// Version. - public static Version ConvertInstalledWinGetVersion() - { - return ConvertWinGetVersion(InstalledWinGetVersion); - } - /// /// Converts a WinGet string format version to a Version object. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs index ff791973e5..5ada78fc8f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs @@ -159,6 +159,15 @@ internal static string IntegrityOsNotSupportedMessage { } } + /// + /// Looks up a localized string similar to The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}'. + /// + internal static string IntegrityUnexpectedVersionMessage { + get { + return ResourceManager.GetString("IntegrityUnexpectedVersionMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unable to execute winget command.. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx index 4973718636..1c7516e794 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx @@ -196,4 +196,7 @@ Windows Package Manager not supported. + + The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}' + \ No newline at end of file From b4a35fdeca5aa6f10215e414440ad4fd8d31b3e5 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 11 Jan 2023 14:51:30 -0800 Subject: [PATCH 06/11] Fix e2e --- .github/actions/spelling/expect.txt | 3 ++ .../Commands/RepairCommand.cs | 27 +++++----- .../Common/RepairResult.cs | 54 ------------------- .../Common/WinGetIntegrity.cs | 48 +++++++++-------- .../Helpers/WingetCLIWrapper.cs | 2 +- 5 files changed, 43 insertions(+), 91 deletions(-) delete mode 100644 src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 6d917039fb..f90a09bb0c 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -246,6 +246,7 @@ PCCERT PCs pcwsz PEGI +PFM pfn pfxpath Pherson @@ -263,6 +264,7 @@ processthreads productcode pscustomobject pseudocode +PSHOST psobject ptstr publickey @@ -281,6 +283,7 @@ REFIID regexes REGSAM relativefilepath +remoting reparse restsource rgex diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs index 879ca7c4dd..d3b80649fc 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -17,9 +17,13 @@ namespace Microsoft.WinGet.Client.Commands /// Repair-WinGet. Repairs winget if needed. /// [Cmdlet(VerbsDiagnostic.Repair, Constants.WinGetNouns.WinGet)] + [OutputType(typeof(int))] public class RepairCommand : BaseCommand { private const string EnvPath = "env:PATH"; + private const int Succeeded = 0; + private const int Failed = -1; + private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; /// @@ -40,7 +44,7 @@ public class RepairCommand : BaseCommand /// protected override void ProcessRecord() { - RepairResult repairResult = RepairResult.Failure; + int result = Failed; var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, this.Version); @@ -51,12 +55,12 @@ protected override void ProcessRecord() if (integrityCategory == IntegrityCategory.Installed || integrityCategory == IntegrityCategory.UnexpectedVersion) { - repairResult = this.RepairForInstalled(preRelease, WinGetVersionHelper.InstalledWinGetVersion, this.Version); + result = this.RepairForInstalled(preRelease, WinGetVersionHelper.InstalledWinGetVersion, this.Version); } else if (integrityCategory == IntegrityCategory.NotInPath) { this.RepairEnvPath(); - repairResult = RepairResult.PathUpdated; + result = Succeeded; } else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) { @@ -64,7 +68,7 @@ protected override void ProcessRecord() appxModule.RegisterAppInstaller(); this.WriteDebug($"WinGet version {WinGetVersionHelper.InstalledWinGetVersion} registered"); - repairResult = RepairResult.Registered; + result = Succeeded; } else if (integrityCategory == IntegrityCategory.AppInstallerNotInstalled || integrityCategory == IntegrityCategory.AppInstallerNotSupported || @@ -72,27 +76,24 @@ protected override void ProcessRecord() { if (this.DownloadAndInstall(preRelease, this.Version, false)) { - repairResult = RepairResult.Installed; + result = Succeeded; } } else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) { // Sorry, but the user has to manually enabled it. this.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); - repairResult = RepairResult.NeedsManualRepair; } else { this.WriteInformation(Resources.WinGetNotSupportedMessage, WriteInformationTags); } - this.WriteObject(repairResult); + this.WriteObject(result); } - private RepairResult RepairForInstalled(bool preRelease, string installedVersion, string toInstallVersion) + private int RepairForInstalled(bool preRelease, string installedVersion, string toInstallVersion) { - RepairResult repairResult = RepairResult.Failure; - if (string.IsNullOrEmpty(toInstallVersion)) { var gitHubRelease = new GitHubRelease(); @@ -115,16 +116,16 @@ private RepairResult RepairForInstalled(bool preRelease, string installedVersion if (this.DownloadAndInstall(preRelease, toInstallVersion, downgrade)) { - repairResult = downgrade ? RepairResult.Downgraded : RepairResult.Updated; + return Succeeded; } } else { this.WriteDebug($"Installed WinGet version and target match {WinGetVersionHelper.InstalledWinGetVersion}"); - repairResult = RepairResult.Noop; + return Succeeded; } - return repairResult; + return Failed; } private bool DownloadAndInstall(bool preRelease, string versionTag, bool downgrade) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs deleted file mode 100644 index efce0cccf8..0000000000 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/RepairResult.cs +++ /dev/null @@ -1,54 +0,0 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Common -{ - /// - /// Repair result. - /// - public enum RepairResult - { - /// - /// Failure. - /// - Failure, - - /// - /// No changes were made. - /// - Noop, - - /// - /// WinGet installed. - /// - Installed, - - /// - /// WinGet registered. - /// - Registered, - - /// - /// WinGet updated. - /// - Updated, - - /// - /// WinGet downgraded. - /// - Downgraded, - - /// - /// Path environment fixed. - /// - PathUpdated, - - /// - /// NeedsManual repair. - /// - NeedsManualRepair, - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs index c1f16ebc61..979fdbc91c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -23,9 +23,9 @@ internal static class WinGetIntegrity /// /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. /// - /// The calling cmdlet. + /// The calling cmdlet. /// Expected version. - public static void AssertWinGet(PSCmdlet pSCmdlet, string expectedVersion) + public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) { try { @@ -34,25 +34,10 @@ public static void AssertWinGet(PSCmdlet pSCmdlet, string expectedVersion) var wingetCliWrapper = new WingetCLIWrapper(false); var result = wingetCliWrapper.RunCommand("--version"); result.VerifyExitCode(); - - if (!string.IsNullOrEmpty(expectedVersion)) - { - // Verify version - var installedVersion = WinGetVersionHelper.InstalledWinGetVersion; - if (expectedVersion != installedVersion) - { - throw new WinGetIntegrityException( - IntegrityCategory.UnexpectedVersion, - string.Format( - Resources.IntegrityUnexpectedVersionMessage, - installedVersion, - expectedVersion)); - } - } } catch (Win32Exception) { - throw new WinGetIntegrityException(GetReason(pSCmdlet)); + throw new WinGetIntegrityException(GetReason(psCmdlet)); } catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) { @@ -62,19 +47,36 @@ public static void AssertWinGet(PSCmdlet pSCmdlet, string expectedVersion) { throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); } + + // WinGet is installed. Verify version if needed. + if (!string.IsNullOrEmpty(expectedVersion)) + { + // Verify version + // TODO: we could also validate if the given version is valid (look at GitHub release). + var installedVersion = WinGetVersionHelper.InstalledWinGetVersion; + if (expectedVersion != installedVersion) + { + throw new WinGetIntegrityException( + IntegrityCategory.UnexpectedVersion, + string.Format( + Resources.IntegrityUnexpectedVersionMessage, + installedVersion, + expectedVersion)); + } + } } /// /// Verifies winget runs correctly. /// - /// The calling cmdlet. + /// The calling cmdlet. /// Expected version. /// Integrity category. - public static IntegrityCategory GetIntegrityCategory(PSCmdlet pSCmdlet, string expectedVersion) + public static IntegrityCategory GetIntegrityCategory(PSCmdlet psCmdlet, string expectedVersion) { try { - AssertWinGet(pSCmdlet, expectedVersion); + AssertWinGet(psCmdlet, expectedVersion); } catch (WinGetIntegrityException e) { @@ -84,7 +86,7 @@ public static IntegrityCategory GetIntegrityCategory(PSCmdlet pSCmdlet, string e return IntegrityCategory.Installed; } - private static IntegrityCategory GetReason(PSCmdlet pSCmdlet) + private static IntegrityCategory GetReason(PSCmdlet psCmdlet) { // Ok, so you are here because calling winget --version failed. Lets try to figure out why. @@ -123,7 +125,7 @@ private static IntegrityCategory GetReason(PSCmdlet pSCmdlet) // It could be that AppInstaller package is old or the package is not // registered at this point. To know that, call Get-AppxPackage. - var appxModule = new AppxModuleHelper(pSCmdlet); + var appxModule = new AppxModuleHelper(psCmdlet); string version = appxModule.GetAppInstallerPropertyValue("Version"); if (version is null) { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs index 56ed83f0e9..c7695928d7 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs @@ -66,7 +66,7 @@ public static string WinGetFullPath public WinGetCLICommandResult RunCommand(string command, string parameters = null, int timeOut = 60000) { string args = command; - if (string.IsNullOrEmpty(parameters)) + if (!string.IsNullOrEmpty(parameters)) { args += ' ' + parameters; } From f7636843e01673859f987be6f660b9abd83981db Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 11 Jan 2023 19:23:24 -0800 Subject: [PATCH 07/11] Add DSC resource --- .../Commands/AssertIntegrityCommand.cs | 27 ++-- .../Commands/Common/BaseIntegrityCommand.cs | 41 ++++++ .../Commands/RepairCommand.cs | 102 +++++++------- .../Common/Constants.cs | 10 ++ .../Common/WinGetIntegrity.cs | 2 - .../Helpers/GitHubRelease.cs | 20 +-- .../Microsoft.WinGet.DSC.psd1 | 1 + .../Microsoft.WinGet.DSC.psm1 | 86 ++++++++++++ .../samples/WinGetIntegrityResourceSample.ps1 | 129 ++++++++++++++++++ 9 files changed, 344 insertions(+), 74 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs create mode 100644 src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs index c60e42bf9a..7b6ec1052b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs @@ -9,26 +9,35 @@ 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; /// /// Assert-WinGetIntegrity. Verifies winget is installed properly. /// - [Cmdlet(VerbsLifecycle.Assert, Constants.WinGetNouns.Integrity)] - public class AssertIntegrityCommand : BaseCommand + [Cmdlet( + VerbsLifecycle.Assert, + Constants.WinGetNouns.Integrity, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + public class AssertIntegrityCommand : BaseIntegrityCommand { - /// - /// Gets or sets the optional version. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } = string.Empty; - /// /// Validates winget is installed correctly. If not, throws an exception /// with the reason why, if any. /// protected override void ProcessRecord() { - WinGetIntegrity.AssertWinGet(this, this.Version); + 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); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs new file mode 100644 index 0000000000..729572022a --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// +// 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-WinGetIntegrity and Repair-WinGet. + /// + public abstract class BaseIntegrityCommand : BaseCommand + { + /// + /// 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; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs index d3b80649fc..abd4c7f71b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -16,9 +16,12 @@ namespace Microsoft.WinGet.Client.Commands /// /// Repair-WinGet. Repairs winget if needed. /// - [Cmdlet(VerbsDiagnostic.Repair, Constants.WinGetNouns.WinGet)] + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.WinGet, + DefaultParameterSetName = Constants.IntegrityVersionSet)] [OutputType(typeof(int))] - public class RepairCommand : BaseCommand + public class RepairCommand : BaseIntegrityCommand { private const string EnvPath = "env:PATH"; private const int Succeeded = 0; @@ -26,18 +29,6 @@ public class RepairCommand : BaseCommand private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; - /// - /// Gets or sets the optional version. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether to include prerelease winget versions. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludePreRelease { get; set; } - /// /// Attempts to repair winget. /// TODO: consider WhatIf and Confirm options. @@ -46,38 +37,52 @@ protected override void ProcessRecord() { int result = Failed; - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, this.Version); + 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}"); - bool preRelease = this.IncludePreRelease.ToBool(); - if (integrityCategory == IntegrityCategory.Installed || integrityCategory == IntegrityCategory.UnexpectedVersion) { - result = this.RepairForInstalled(preRelease, WinGetVersionHelper.InstalledWinGetVersion, this.Version); + result = this.VerifyWinGetInstall(integrityCategory, expectedVersion); } else if (integrityCategory == IntegrityCategory.NotInPath) { this.RepairEnvPath(); - result = Succeeded; + + // 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(); - this.WriteDebug($"WinGet version {WinGetVersionHelper.InstalledWinGetVersion} registered"); - result = Succeeded; + // 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 (this.DownloadAndInstall(preRelease, this.Version, false)) + if (this.DownloadAndInstall(this.Version, false)) { result = Succeeded; } + else + { + this.WriteDebug($"Failed installing {expectedVersion}"); + } } else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) { @@ -92,49 +97,52 @@ protected override void ProcessRecord() this.WriteObject(result); } - private int RepairForInstalled(bool preRelease, string installedVersion, string toInstallVersion) + private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expectedVersion) { - if (string.IsNullOrEmpty(toInstallVersion)) + if (integrityCategory == IntegrityCategory.Installed) { - var gitHubRelease = new GitHubRelease(); - toInstallVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + // Nothing to do + this.WriteDebug($"WinGet is in a good state."); + return Succeeded; } - - if (toInstallVersion != installedVersion) + else if (integrityCategory == IntegrityCategory.UnexpectedVersion) { - this.WriteDebug($"Installed WinGet version {installedVersion}"); - this.WriteDebug($"Installing WinGet version {toInstallVersion}"); - - var v1 = WinGetVersionHelper.ConvertWinGetVersion(installedVersion); - var v2 = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); - - bool downgrade = false; - if (v1.CompareTo(v2) > 0) + // The versions are different, download and install. + if (!this.InstallDifferentVersion(WinGetVersionHelper.InstalledWinGetVersion, expectedVersion)) { - downgrade = true; + this.WriteDebug($"Failed installing {expectedVersion}"); } - - if (this.DownloadAndInstall(preRelease, toInstallVersion, downgrade)) + else { return Succeeded; } } - else + + return Failed; + } + + private bool InstallDifferentVersion(string installedVersion, string toInstallVersion) + { + this.WriteDebug($"Installed WinGet version {installedVersion}"); + this.WriteDebug($"Installing WinGet version {toInstallVersion}"); + + var v1 = WinGetVersionHelper.ConvertWinGetVersion(installedVersion); + var v2 = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); + + bool downgrade = false; + if (v1.CompareTo(v2) > 0) { - this.WriteDebug($"Installed WinGet version and target match {WinGetVersionHelper.InstalledWinGetVersion}"); - return Succeeded; + downgrade = true; } - return Failed; + return this.DownloadAndInstall(toInstallVersion, downgrade); } - private bool DownloadAndInstall(bool preRelease, string versionTag, bool downgrade) + private bool DownloadAndInstall(string versionTag, bool downgrade) { // Download and install. var gitHubRelease = new GitHubRelease(); - var downloadedMsixBundlePath = gitHubRelease.DownloadRelease( - preRelease, - versionTag); + var downloadedMsixBundlePath = gitHubRelease.DownloadRelease(versionTag); var appxModule = new AppxModuleHelper(this); appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs index 92ea69f321..62f8465236 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs @@ -34,6 +34,16 @@ internal static class Constants /// public const string FoundSet = "FoundSet"; + /// + /// Parameter set for an specific version parameter. + /// + public const string IntegrityVersionSet = "IntegrityVersionSet"; + + /// + /// Parameter set for an latest version with optional prereleased version. + /// + public const string IntegrityLatestSet = "IntegrityLatestSet"; + /// /// WinGet package family name. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs index 979fdbc91c..efd7b5438a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -51,8 +51,6 @@ public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) // WinGet is installed. Verify version if needed. if (!string.IsNullOrEmpty(expectedVersion)) { - // Verify version - // TODO: we could also validate if the given version is valid (look at GitHub release). var installedVersion = WinGetVersionHelper.InstalledWinGetVersion; if (expectedVersion != installedVersion) { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs index b5663fc9fd..3e96d1c1c0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs @@ -38,12 +38,11 @@ public GitHubRelease() /// /// Download a release from winget-cli. /// - /// Include prerelease. /// Optional release name. If null, gets latest. /// Path where the msix bundle is downloaded. - public string DownloadRelease(bool includePreRelease, string releaseTag = null) + public string DownloadRelease(string releaseTag) { - return this.DownloadReleaseAsync(includePreRelease, releaseTag).GetAwaiter().GetResult(); + return this.DownloadReleaseAsync(releaseTag).GetAwaiter().GetResult(); } /// @@ -69,22 +68,11 @@ public void DownloadUrl(string url, string fileName) /// /// Download asynchronously a release from winget-cli. /// - /// Include prerelease. /// Optional release name. If null, gets latest. /// Path where the msix bundle is downloaded. - public async Task DownloadReleaseAsync(bool includePreRelease, string releaseTag = null) + public async Task DownloadReleaseAsync(string releaseTag) { - Release release; - - // If not specified get the latest. - if (string.IsNullOrEmpty(releaseTag)) - { - release = await this.GetLatestVersionAsync(includePreRelease); - } - else - { - release = await this.gitHubClient.Repository.Release.Get(Owner, Repo, releaseTag); - } + Release release = await this.gitHubClient.Repository.Release.Get(Owner, Repo, releaseTag); // Get asset and download. var msixBundleAsset = release.Assets.Where(a => a.Name == MsixBundleName).First(); diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 index 4fde00d764..9aff328fa0 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 @@ -85,6 +85,7 @@ 'WinGetUserSettingsResource' 'WinGetAdminSettings' 'WinGetSourcesResource' + 'WinGetIntegrityResource' ) # List of all modules packaged with this module diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 index 0e1a23842c..906b1d62c5 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 @@ -304,4 +304,90 @@ class WinGetSourcesResource } } +[DSCResource()] +class WinGetIntegrityResource +{ + # We need a key. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty()] + [string]$Version = "" + + [DscProperty()] + [bool]$Latest + + [DscProperty()] + [bool]$LatestPreRelease + + # If winget is not installed the version will be empty. + [WinGetIntegrityResource] Get() + { + $integrityResource = [WinGetIntegrityResource]::new() + if ($integrityResource.Test()) + { + $integrityResource.Version = Get-WinGetVersion + } + + return $integrityResource + } + + # Tests winget is installed. + [bool] Test() + { + Assert-WinGetCommand "Assert-WinGetIntegrity" + Assert-WinGetCommand "Get-WinGetVersion" + + try + { + if ($this.Latest) + { + Assert-WinGetIntegrity -Latest + } + elseif ($this.LatestPreRelease) + { + Assert-WinGetIntegrity -Latest -IncludePreRelease + } + else + { + Assert-WinGetIntegrity -Version $this.Version + } + } + catch + { + return $false + } + + return $true + } + + # Repairs Winget. + [void] Set() + { + Assert-WinGetCommand "Repair-WinGet" + + if (-not $this.Test()) + { + $result = -1 + if ($this.Latest) + { + $result = Repair-WinGet -Latest + } + elseif ($this.LatestPreRelease) + { + $result = Repair-WinGet -Latest -IncludePreRelease + } + else + { + $result = Repair-WinGet -Version $this.Version + } + + if ($result -ne 0) + { + throw "Failed to repair winget." + } + } + } +} + #endregion DscResources diff --git a/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 new file mode 100644 index 0000000000..696b21e99d --- /dev/null +++ b/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 @@ -0,0 +1,129 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Simple sample on how to use WinGetIntegrity DSC resource. + Requires PSDesiredStateConfiguration v2 and enabling the + PSDesiredStateConfiguration.InvokeDscResource experimental feature + `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` + + IMPORTANT: This will modify the winget you have installed +#> + +#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC + +using module Microsoft.WinGet.DSC + +$resource = @{ + Name = 'WinGetIntegrityResource' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + } +} + +# This just demostrate the Get command. +$getResult = Invoke-DscResource @resource -Method Get + +if (-not([string]::IsNullOrWhiteSpace($getResult.Version))) +{ + Write-Host "Current winget version $($getResult.Version)" +} +else +{ + # If the result of get contains an empty version, it means that winget is not installed. This is not the right + # way to do it though, is better to call Test with an empty Version property. + if (-not (Invoke-DscResource @resource -Method Test).InDesiredState) + { + Write-Host "winget is not installed" + } + else + { + Write-Error "BUG BUG!!" + return + } +} + +# At the time I'm doing this the second latest released winget version is v1.3.2091. Lets assume you want to stay there forever. +$v132091 = "v1.3.2091" +$resource = @{ + Name = 'WinGetIntegrityResource' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Version = $v132091 + } +} + +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget is already in a good state (aka in version $v132091)" +} +else +{ + # Oh no, we are not in a good state. Lets get you there. + # Internally, Set calls Repair-WinGet -Version v1.3.2691 which means that it will try to repair winget + # by downloading v1.3.2691 and installing it if needed. For example, if your AppInstaller is not registered + # it will register the package and then verify the specified version is installed. + Invoke-DscResource @resource -Method Set | Out-Null + + # Now this should work. + $testResult = Invoke-DscResource @resource -Method Test + if ($testResult.InDesiredState) + { + Write-Host "winget is in a good state (aka in version $v132091)" + } + else + { + Write-Error "BUG BUG!!" + return + } +} + +# Now, lets say that you want to have always the latest winget installed. You can specify the Latest. +$resource = @{ + Name = 'WinGetIntegrityResource' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Latest = $true + } +} +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget version is the latest version" +} +else +{ + Write-Host "winget version is not latest version" +} + +# You can also do LatestPreRelease +$resource = @{ + Name = 'WinGetIntegrityResource' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + LatestPreRelease = $true + } +} +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget version is the latest prereleased version" +} +else +{ + # Get the latest prereleased. + Invoke-DscResource @resource -Method Set | Out-Null + + $testResult = Invoke-DscResource @resource -Method Test + if ($testResult.InDesiredState) + { + Write-Host "winget version is the latest prereleased version" + } + else + { + Write-Error "BUG BUG!!" + return + } +} From 68a9620ac6a959b48bae6ce1ffe1c91c67f0074a Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Wed, 11 Jan 2023 20:00:46 -0800 Subject: [PATCH 08/11] PR comments --- .../Commands/RepairCommand.cs | 11 ++- .../Common/Constants.cs | 82 +++++++++---------- .../Common/WinGetIntegrity.cs | 2 +- .../Helpers/AppxModuleHelper.cs | 7 +- .../Helpers/WinGetVersionHelper.cs | 6 +- .../Properties/Resources.resx | 18 ++-- .../samples/WinGetIntegrityResourceSample.ps1 | 8 +- 7 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs index abd4c7f71b..f39b508cb2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs @@ -75,7 +75,16 @@ protected override void ProcessRecord() integrityCategory == IntegrityCategory.AppInstallerNotSupported || integrityCategory == IntegrityCategory.Failure) { - if (this.DownloadAndInstall(this.Version, false)) + // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGet. + // 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; } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs index 62f8465236..5f7440e00e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs @@ -32,48 +32,48 @@ 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. /// - public const string FoundSet = "FoundSet"; - - /// - /// Parameter set for an specific version parameter. - /// - public const string IntegrityVersionSet = "IntegrityVersionSet"; - - /// - /// Parameter set for an latest version with optional prereleased version. - /// - public const string IntegrityLatestSet = "IntegrityLatestSet"; - - /// - /// WinGet package family name. - /// -#if USE_PROD_CLSIDS - public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; -#else - public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; -#endif - - /// - /// Winget executable name. - /// -#if USE_PROD_CLSIDS - public const string WinGetExe = "winget.exe"; -#else - public const string WinGetExe = "wingetdev.exe"; -#endif - - /// - /// Name of PATH environment variable. - /// - public const string PathEnvVar = "PATH"; - + public const string FoundSet = "FoundSet"; + + /// + /// Parameter set for an specific version parameter. + /// + public const string IntegrityVersionSet = "IntegrityVersionSet"; + + /// + /// Parameter set for an latest version with optional prerelease version. + /// + public const string IntegrityLatestSet = "IntegrityLatestSet"; + + /// + /// WinGet package family name. + /// +#if USE_PROD_CLSIDS + public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; +#else + public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; +#endif + + /// + /// Winget executable name. + /// +#if USE_PROD_CLSIDS + public const string WinGetExe = "winget.exe"; +#else + public const string WinGetExe = "wingetdev.exe"; +#endif + + /// + /// Name of PATH environment variable. + /// + public const string PathEnvVar = "PATH"; + /// /// Nouns used for different cmdlets. Changing this will alter the names of the related commands. /// public static class WinGetNouns { - /// - /// WinGet. + /// + /// WinGet. /// public const string WinGet = "WinGet"; @@ -92,13 +92,13 @@ public static class WinGetNouns /// public const string UserSettings = "WinGetUserSettings"; - /// - /// The noun for winget version. + /// + /// The noun for winget version. /// public const string Version = "WinGetVersion"; - /// - /// The noun for winget integrity. + /// + /// The noun for winget integrity. /// public const string Integrity = "WinGetIntegrity"; } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs index efd7b5438a..90ab7cf573 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -91,7 +91,7 @@ private static IntegrityCategory GetReason(PSCmdlet psCmdlet) // First lets check if the file is there, which means it is installed or someone is taking our place. if (File.Exists(WingetCLIWrapper.WinGetFullPath)) { - // The file exists, but we couldn't call it... We'll maybe winget's app execution alias is not enabled. + // The file exists, but we couldn't call it... Well maybe winget's app execution alias is not enabled. // The trick is knowing that a magical file appears under WindowsApp when its enabled. string wingetAliasPath = Path.Combine(Utilities.LocalDataWindowsAppPath, Constants.WinGetExe); if (File.Exists(wingetAliasPath)) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs index 892aa0ba71..1378a3ceea 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -162,13 +162,8 @@ private void InstallVCLibsDependencies() } var tmpFile = Path.GetTempFileName(); - - // This is weird but easy. - var githubRelease = new GitHubRelease(); - githubRelease.DownloadUrl(url, tmpFile); - this.psCmdlet.WriteDebug($"Installing VCLibs {url}"); - this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, tmpFile)); + this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, url)); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs index c07e9d1ab0..447241604e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs @@ -39,13 +39,11 @@ public static Version ConvertWinGetVersion(string version) } // WinGet version starts with v - if (version[0] != 'v') + if (version[0] == 'v') { - throw new ArgumentException(); + version = version.Substring(1); } - version = version.Substring(1); - // WinGet version might end with -preview if (version.EndsWith("-preview")) { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx index 1c7516e794..f441e6a170 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx @@ -119,46 +119,47 @@ No source matches the given value: {0} - {0} - The name of the source that was not found. + {Locked="{0}"} {0} - The name of the source that was not found. This cmdlet is currently disabled for SYSTEM. An error occurred while searching for packages: {0} - {0} - A string representation of the error status returned by the catalog. + {Locked="{0}"} {0} - A string representation of the error status returned by the catalog. Installing '{0}' - {0} - The name of the package being installed. + {Locked="{0}"} {0} - The name of the package being installed. Uninstalling '{0}' - {0} - The name of the package being uninstalled. + {Locked="{0}"} {0} - The name of the package being uninstalled. Updating '{0}' - {0} - The name of the package being updated. + {Locked="{0}"} {0} - The name of the package being updated. An error occurred while connecting to the catalog. No versions matched the given value: {0} - {0} - The version string provided by the user. + {Locked="{0}"} {0} - The version string provided by the user. No packages matched the given input criteria. {0}, {1}, and {2} other packages matched the input criteria. Please refine the input. - {0} - The first conflicting package as a string. {1} - The second conflicting package. {2} - The number of other packages that also matched the input criteria. + {Locked="{0}","{1}","{2}"} {0} - The first conflicting package as a string. {1} - The second conflicting package. {2} - The number of other packages that also matched the input criteria. Unable to execute winget command. Command {0} failed with exit code {1} + {Locked="{0}","{1}"} {0} - The winget command executed. {1} - The exit code. User settings file is invalid. @@ -183,6 +184,7 @@ Winget command run timed out: {0} {1} + {Locked="{0}","{1}"} {0} - The winget command executed. {1} - The parameters of the command. The App Installer is not registered. @@ -192,11 +194,13 @@ Microsoft.UI.Xaml.2.7 package is not installed + {Locked="Microsoft.UI.Xaml.2.7"} Windows Package Manager not supported. The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}' + {Locked="{0}","{1}"} {0} - The winget current installed winget version. {1} - The expected winget version. \ No newline at end of file diff --git a/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 index 696b21e99d..4efcdaf78a 100644 --- a/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 @@ -22,7 +22,7 @@ $resource = @{ } } -# This just demostrate the Get command. +# This just demonstrate the Get command. $getResult = Invoke-DscResource @resource -Method Get if (-not([string]::IsNullOrWhiteSpace($getResult.Version))) @@ -109,17 +109,17 @@ $resource = @{ $testResult = Invoke-DscResource @resource -Method Test if ($testResult.InDesiredState) { - Write-Host "winget version is the latest prereleased version" + Write-Host "winget version is the latest prerelease version" } else { - # Get the latest prereleased. + # Get the latest prerelease. Invoke-DscResource @resource -Method Set | Out-Null $testResult = Invoke-DscResource @resource -Method Test if ($testResult.InDesiredState) { - Write-Host "winget version is the latest prereleased version" + Write-Host "winget version is the latest prerelease version" } else { From 2cb59e7f730ed19ed22b079e30ae73f6e8abd8f7 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Date: Fri, 24 Feb 2023 10:05:55 -0800 Subject: [PATCH 09/11] Update src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx Co-authored-by: JohnMcPMS --- .../Microsoft.WinGet.Client/Properties/Resources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx index f441e6a170..abe71c3060 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx @@ -180,7 +180,7 @@ The App Installer did not automatically add the PATH environment variable. - The Windows Package Manager requires at least Version 1809 (October 2018 Update). + The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later. Winget command run timed out: {0} {1} From 831d74324aee2101f749e6ab0463d3ef9539b31f Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Mon, 6 Mar 2023 18:14:09 -0800 Subject: [PATCH 10/11] Rename resources. Add ARM64 support. Fix WinGetVersion. Fix getting version in resource --- ...s => AssertWinGetPackageManagerCommand.cs} | 8 +- .../Commands/Common/BaseIntegrityCommand.cs | 2 +- .../Commands/GetVersionCommand.cs | 2 +- ...s => RepairWinGetPackageManagerCommand.cs} | 25 ++-- .../Common/Constants.cs | 7 +- .../Common/WinGetIntegrity.cs | 7 +- .../Helpers/AppxModuleHelper.cs | 72 ++++++++++- .../Helpers/WinGetVersion.cs | 118 ++++++++++++++++++ .../Helpers/WinGetVersionHelper.cs | 56 --------- .../Module/Microsoft.WinGet.Client.psd1 | 4 +- .../Microsoft.WinGet.DSC.psd1 | 6 +- .../Microsoft.WinGet.DSC.psm1 | 66 +++++----- .../scripts/Initialize-LocalWinGetModules.ps1 | 14 ++- .../WinGetAdminSettingsResourceSample.ps1 | 5 +- ...ple.ps1 => WinGetPackageManagerSample.ps1} | 20 ++- ...urceSample.ps1 => WinGetSourcesSample.ps1} | 5 +- ...inGetUserSettingsPackageManagerSample.ps1} | 6 +- 17 files changed, 274 insertions(+), 149 deletions(-) rename src/PowerShell/Microsoft.WinGet.Client/Commands/{AssertIntegrityCommand.cs => AssertWinGetPackageManagerCommand.cs} (80%) rename src/PowerShell/Microsoft.WinGet.Client/Commands/{RepairCommand.cs => RepairWinGetPackageManagerCommand.cs} (87%) create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersion.cs delete mode 100644 src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs rename src/PowerShell/scripts/samples/{WinGetIntegrityResourceSample.ps1 => WinGetPackageManagerSample.ps1} (83%) rename src/PowerShell/scripts/samples/{WinGetSourcesResourceSample.ps1 => WinGetSourcesSample.ps1} (88%) rename src/PowerShell/scripts/samples/{WinGetUserSettingsResourceSample.ps1 => WinGetUserSettingsPackageManagerSample.ps1} (83%) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertWinGetPackageManagerCommand.cs similarity index 80% rename from src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs rename to src/PowerShell/Microsoft.WinGet.Client/Commands/AssertWinGetPackageManagerCommand.cs index 7b6ec1052b..6cfd6cea9b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertIntegrityCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertWinGetPackageManagerCommand.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------------- -// +// // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // // ----------------------------------------------------------------------------- @@ -12,13 +12,13 @@ namespace Microsoft.WinGet.Client.Commands using Microsoft.WinGet.Client.Helpers; /// - /// Assert-WinGetIntegrity. Verifies winget is installed properly. + /// Assert-WinGetPackageManager. Verifies winget is installed properly. /// [Cmdlet( VerbsLifecycle.Assert, - Constants.WinGetNouns.Integrity, + Constants.WinGetNouns.WinGetPackageManager, DefaultParameterSetName = Constants.IntegrityVersionSet)] - public class AssertIntegrityCommand : BaseIntegrityCommand + public class AssertWinGetPackageManagerCommand : BaseIntegrityCommand { /// /// Validates winget is installed correctly. If not, throws an exception diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs index 729572022a..59cf7f971f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs @@ -10,7 +10,7 @@ namespace Microsoft.WinGet.Client.Commands.Common using Microsoft.WinGet.Client.Common; /// - /// Common parameters for Assert-WinGetIntegrity and Repair-WinGet. + /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. /// public abstract class BaseIntegrityCommand : BaseCommand { diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs index f91377d4cd..5253713186 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs @@ -23,7 +23,7 @@ public class GetVersionCommand : BaseCommand /// protected override void ProcessRecord() { - this.WriteObject(WinGetVersionHelper.InstalledWinGetVersion); + this.WriteObject(WinGetVersion.InstalledWinGetVersion.TagVersion); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairWinGetPackageManagerCommand.cs similarity index 87% rename from src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs rename to src/PowerShell/Microsoft.WinGet.Client/Commands/RepairWinGetPackageManagerCommand.cs index f39b508cb2..22c2a8b3f5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairWinGetPackageManagerCommand.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------------- -// +// // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // // ----------------------------------------------------------------------------- @@ -14,14 +14,14 @@ namespace Microsoft.WinGet.Client.Commands using Microsoft.WinGet.Client.Properties; /// - /// Repair-WinGet. Repairs winget if needed. + /// Repair-WinGetPackageManager. Repairs winget if needed. /// [Cmdlet( VerbsDiagnostic.Repair, - Constants.WinGetNouns.WinGet, + Constants.WinGetNouns.WinGetPackageManager, DefaultParameterSetName = Constants.IntegrityVersionSet)] [OutputType(typeof(int))] - public class RepairCommand : BaseIntegrityCommand + public class RepairWinGetPackageManagerCommand : BaseIntegrityCommand { private const string EnvPath = "env:PATH"; private const int Succeeded = 0; @@ -75,7 +75,7 @@ protected override void ProcessRecord() integrityCategory == IntegrityCategory.AppInstallerNotSupported || integrityCategory == IntegrityCategory.Failure) { - // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGet. + // 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)) @@ -117,7 +117,7 @@ private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expe else if (integrityCategory == IntegrityCategory.UnexpectedVersion) { // The versions are different, download and install. - if (!this.InstallDifferentVersion(WinGetVersionHelper.InstalledWinGetVersion, expectedVersion)) + if (!this.InstallDifferentVersion(new WinGetVersion(expectedVersion))) { this.WriteDebug($"Failed installing {expectedVersion}"); } @@ -130,21 +130,20 @@ private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expe return Failed; } - private bool InstallDifferentVersion(string installedVersion, string toInstallVersion) + private bool InstallDifferentVersion(WinGetVersion toInstallVersion) { - this.WriteDebug($"Installed WinGet version {installedVersion}"); - this.WriteDebug($"Installing WinGet version {toInstallVersion}"); + var installedVersion = WinGetVersion.InstalledWinGetVersion; - var v1 = WinGetVersionHelper.ConvertWinGetVersion(installedVersion); - var v2 = WinGetVersionHelper.ConvertWinGetVersion(toInstallVersion); + this.WriteDebug($"Installed WinGet version {installedVersion.TagVersion}"); + this.WriteDebug($"Installing WinGet version {toInstallVersion.TagVersion}"); bool downgrade = false; - if (v1.CompareTo(v2) > 0) + if (installedVersion.CompareAsDeployment(toInstallVersion) > 0) { downgrade = true; } - return this.DownloadAndInstall(toInstallVersion, downgrade); + return this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); } private bool DownloadAndInstall(string versionTag, bool downgrade) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs index 5f7440e00e..31f03f65f9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs @@ -75,7 +75,7 @@ public static class WinGetNouns /// /// WinGet. /// - public const string WinGet = "WinGet"; + public const string WinGetPackageManager = "WinGetPackageManager"; /// /// The noun analogue of the class. @@ -96,11 +96,6 @@ public static class WinGetNouns /// The noun for winget version. /// public const string Version = "WinGetVersion"; - - /// - /// The noun for winget integrity. - /// - public const string Integrity = "WinGetIntegrity"; } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs index 90ab7cf573..a5a8954cd9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/WinGetIntegrity.cs @@ -9,7 +9,6 @@ namespace Microsoft.WinGet.Client.Common using System; using System.ComponentModel; using System.IO; - using System.Linq; using System.Management.Automation; using Microsoft.WinGet.Client.Exceptions; using Microsoft.WinGet.Client.Helpers; @@ -51,8 +50,10 @@ public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) // WinGet is installed. Verify version if needed. if (!string.IsNullOrEmpty(expectedVersion)) { - var installedVersion = WinGetVersionHelper.InstalledWinGetVersion; - if (expectedVersion != installedVersion) + // This assumes caller knows that the version exist. + WinGetVersion expectedWinGetVersion = new WinGetVersion(expectedVersion); + var installedVersion = WinGetVersion.InstalledWinGetVersion; + if (expectedWinGetVersion.CompareTo(installedVersion) != 0) { throw new WinGetIntegrityException( IntegrityCategory.UnexpectedVersion, diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs index 1378a3ceea..a7043a0aa4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -6,6 +6,7 @@ namespace Microsoft.WinGet.Client.Helpers { + using System.Collections.Generic; using System.IO; using System.Linq; using System.Management.Automation; @@ -35,6 +36,11 @@ internal class AppxModuleHelper // Dependencies private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; private const string VCLibsUWPDesktopVersion = "14.0.30704.0"; + private const string VCLibsUWPDesktopX64 = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"; + private const string VCLibsUWPDesktopX86 = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx"; + private const string VCLibsUWPDesktopArm = "https://aka.ms/Microsoft.VCLibs.arm.14.00.Desktop.appx"; + private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"; + private const string UiXaml27 = "Microsoft.UI.Xaml.2.7"; private readonly PSCmdlet psCmdlet; @@ -97,6 +103,12 @@ public string GetAppInstallerPropertyValue(string propertyName) /// If the package version is lower than the installed one. public void AddAppInstallerBundle(string localPath, bool downgrade = false) { + // A better implementation would use Add-AppxPackage with -DependencyPath, but + // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter + // gets deserialized from Core the result is a single string which breaks Add-AppxPackage. + // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath + // if we are in Core, then start powershell.exe and run the same command. Right now, we just + // do Add-AppxPackage for each one. this.InstallVCLibsDependencies(); this.InstallUiXaml(); @@ -139,6 +151,40 @@ private PSObject GetAppxObject(string packageName) .FirstOrDefault(); } + private IReadOnlyList GetVCLibsDependencies() + { + var vcLibsDependencies = new List(); + var vcLibsPackageObjs = this.psCmdlet.InvokeCommand + .InvokeScript(string.Format(GetAppxPackageByVersionCommand, VCLibsUWPDesktop, VCLibsUWPDesktopVersion)); + if (vcLibsPackageObjs is null || + vcLibsPackageObjs.Count == 0) + { + var arch = RuntimeInformation.OSArchitecture; + if (arch == Architecture.X64) + { + vcLibsDependencies.Add(VCLibsUWPDesktopX64); + } + else if (arch == Architecture.X86) + { + vcLibsDependencies.Add(VCLibsUWPDesktopX86); + } + else if (arch == Architecture.Arm64) + { + // Deployment please figure out for me. + vcLibsDependencies.Add(VCLibsUWPDesktopX64); + vcLibsDependencies.Add(VCLibsUWPDesktopX86); + vcLibsDependencies.Add(VCLibsUWPDesktopArm); + vcLibsDependencies.Add(VCLibsUWPDesktopArm64); + } + else + { + throw new PSNotSupportedException(arch.ToString()); + } + } + + return vcLibsDependencies; + } + private void InstallVCLibsDependencies() { var vcLibsPackageObjs = this.psCmdlet.InvokeCommand @@ -146,24 +192,38 @@ private void InstallVCLibsDependencies() if (vcLibsPackageObjs is null || vcLibsPackageObjs.Count == 0) { + var packages = new List(); + var arch = RuntimeInformation.OSArchitecture; - string url; if (arch == Architecture.X64) { - url = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"; + packages.Add(VCLibsUWPDesktopX64); } else if (arch == Architecture.X86) { - url = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx"; + packages.Add(VCLibsUWPDesktopX86); + } + else if (arch == Architecture.Arm64) + { + packages.Add(VCLibsUWPDesktopX64); + packages.Add(VCLibsUWPDesktopX86); + packages.Add(VCLibsUWPDesktopArm); + packages.Add(VCLibsUWPDesktopArm64); } else { throw new PSNotSupportedException(arch.ToString()); } - var tmpFile = Path.GetTempFileName(); - this.psCmdlet.WriteDebug($"Installing VCLibs {url}"); - this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, url)); + foreach (var package in packages) + { + this.psCmdlet.WriteDebug($"Installing VCLibs {package}"); + this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, package)); + } + } + else + { + this.psCmdlet.WriteDebug($"VCLibs are updated."); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersion.cs new file mode 100644 index 0000000000..65dd303121 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersion.cs @@ -0,0 +1,118 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Helpers +{ + using System; + + /// + /// WinGetVersion. Parse the string version returned by winget --version to allow comparisons. + /// + internal class WinGetVersion + { + /// + /// Initializes a new instance of the class. + /// + /// String Version. + public WinGetVersion(string version) + { + if (string.IsNullOrEmpty(version)) + { + throw new ArgumentNullException(); + } + + string toParseVersion = version; + + // WinGet version starts with v + if (toParseVersion[0] == 'v') + { + this.TagVersion = version; + toParseVersion = toParseVersion.Substring(1); + } + else + { + // WinGet version always start with v. + this.TagVersion = 'v' + version; + } + + // WinGet version might end with -preview + if (toParseVersion.EndsWith("-preview")) + { + this.IsPrerelease = true; + toParseVersion = toParseVersion.Substring(0, toParseVersion.IndexOf('-')); + } + + this.Version = Version.Parse(toParseVersion); + } + + /// + /// Gets the version of the installed winget. + /// + public static WinGetVersion InstalledWinGetVersion + { + get + { + var wingetCliWrapper = new WingetCLIWrapper(); + var result = wingetCliWrapper.RunCommand("--version"); + return new WinGetVersion(result.StdOut.Replace(Environment.NewLine, string.Empty)); + } + } + + /// + /// Gets the version as it appears as a tag. + /// + public string TagVersion { get; } + + /// + /// Gets the version. + /// + public System.Version Version { get; } + + /// + /// Gets a value indicating whether is this version is a prerelease. + /// + public bool IsPrerelease { get; } + + /// + /// Version.CompareTo taking into account prerelease. + /// From semver: Pre-release versions have a lower precedence than the associated normal version. + /// + /// Other winget version. + /// + /// A signed integer that indicates the relative values of the two objects. Less than 0 + /// means this version is before other version. 0 means they are equal. Greater than 0 + /// means this version is greater. + /// + public int CompareTo(WinGetVersion otherVersion) + { + if (this.IsPrerelease && !otherVersion.IsPrerelease) + { + return -1; + } + + if (!this.IsPrerelease && otherVersion.IsPrerelease) + { + return 1; + } + + return this.Version.CompareTo(otherVersion.Version); + } + + /// + /// Deployment doesn't care about semver or prerelease builds. + /// + /// Other version. + /// + /// A signed integer that indicates the relative values of the two objects. Less than 0 + /// means this version is before other version. 0 means they are equal. Greater than 0 + /// means this version is greater. + /// + public int CompareAsDeployment(WinGetVersion otherVersion) + { + return this.Version.CompareTo(otherVersion.Version); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs deleted file mode 100644 index 447241604e..0000000000 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersionHelper.cs +++ /dev/null @@ -1,56 +0,0 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Helpers -{ - using System; - - /// - /// WinGetVersion helper. - /// - internal static class WinGetVersionHelper - { - /// - /// Gets the version of the installed winget. - /// - public static string InstalledWinGetVersion - { - get - { - var wingetCliWrapper = new WingetCLIWrapper(); - var result = wingetCliWrapper.RunCommand("--version"); - return result.StdOut.Replace(Environment.NewLine, string.Empty); - } - } - - /// - /// Converts a WinGet string format version to a Version object. - /// - /// Version string. - /// Version. - public static Version ConvertWinGetVersion(string version) - { - if (string.IsNullOrEmpty(version)) - { - throw new ArgumentNullException(); - } - - // WinGet version starts with v - if (version[0] == 'v') - { - version = version.Substring(1); - } - - // WinGet version might end with -preview - if (version.EndsWith("-preview")) - { - version = version.Substring(0, version.IndexOf('-')); - } - - return Version.Parse(version); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 index 20e1a2c8c9..2b5eda60b4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 @@ -95,8 +95,8 @@ CmdletsToExport = @( 'Get-WinGetUserSettings', 'Set-WinGetUserSettings', 'Test-WinGetUserSettings', - 'Assert-WinGetIntegrity', - 'Repair-WinGet' + 'Assert-WinGetPackageManager', + 'Repair-WinGetPackageManager' ) # Variables to export from this module diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 index 9aff328fa0..c8d6ba1218 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 @@ -82,10 +82,10 @@ # DSC resources to export from this module DscResourcesToExport = @( - 'WinGetUserSettingsResource' + 'WinGetUserSettings' 'WinGetAdminSettings' - 'WinGetSourcesResource' - 'WinGetIntegrityResource' + 'WinGetSources' + 'WinGetPackageManager' ) # List of all modules packaged with this module diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 index 906b1d62c5..74e5ed6fb6 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 @@ -43,7 +43,7 @@ enum Ensure # This resource is in charge of managing the settings.json file of winget. [DSCResource()] -class WinGetUserSettingsResource +class WinGetUserSettings { # We need a key. Do not set. [DscProperty(Key)] @@ -57,7 +57,7 @@ class WinGetUserSettingsResource [WinGetAction]$Action = [WinGetAction]::Full # Gets the current UserSettings by looking at the settings.json file for the current user. - [WinGetUserSettingsResource] Get() + [WinGetUserSettings] Get() { Assert-WinGetCommand "Get-WinGetUserSettings" @@ -172,7 +172,7 @@ class WinGetAdminSettings } [DSCResource()] -class WinGetSourcesResource +class WinGetSources { # We need a key. Do not set. [DscProperty(Key)] @@ -192,7 +192,7 @@ class WinGetSourcesResource [WinGetAction]$Action = [WinGetAction]::Full # Gets the current sources on winget. - [WinGetSourcesResource] Get() + [WinGetSources] Get() { Assert-WinGetCommand "Get-WinGetSource" $packageCatalogReferences = Get-WinGetSource @@ -304,8 +304,10 @@ class WinGetSourcesResource } } +# TODO: It would be nice if these resource has a non configurable property that has extra information that comes from +# GitHub. We could implement it here or add more cmdlets in Microsoft.WinGet.Client. [DSCResource()] -class WinGetIntegrityResource +class WinGetPackageManager { # We need a key. Do not set. [DscProperty(Key)] @@ -315,15 +317,15 @@ class WinGetIntegrityResource [string]$Version = "" [DscProperty()] - [bool]$Latest + [bool]$UseLatest [DscProperty()] - [bool]$LatestPreRelease + [bool]$UseLatestPreRelease # If winget is not installed the version will be empty. - [WinGetIntegrityResource] Get() + [WinGetPackageManager] Get() { - $integrityResource = [WinGetIntegrityResource]::new() + $integrityResource = [WinGetPackageManager]::new() if ($integrityResource.Test()) { $integrityResource.Version = Get-WinGetVersion @@ -335,23 +337,26 @@ class WinGetIntegrityResource # Tests winget is installed. [bool] Test() { - Assert-WinGetCommand "Assert-WinGetIntegrity" + Assert-WinGetCommand "Assert-WinGetPackageManager" Assert-WinGetCommand "Get-WinGetVersion" try { - if ($this.Latest) + $hashArgs = @{} + + if ($this.UseLatest) { - Assert-WinGetIntegrity -Latest - } - elseif ($this.LatestPreRelease) + $hashArgs.Add("Latest", $true) + } elseif ($this.UseLatestPreRelease) { - Assert-WinGetIntegrity -Latest -IncludePreRelease - } - else + $hashArgs.Add("Latest", $true) + $hashArgs.Add("IncludePreRelease", $true) + } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) { - Assert-WinGetIntegrity -Version $this.Version + $hashArgs.Add("Version", $this.Version) } + + Assert-WinGetPackageManager @hashArgs } catch { @@ -364,27 +369,30 @@ class WinGetIntegrityResource # Repairs Winget. [void] Set() { - Assert-WinGetCommand "Repair-WinGet" + Assert-WinGetCommand "Repair-WinGetPackageManager" if (-not $this.Test()) { $result = -1 - if ($this.Latest) + $hashArgs = @{} + + if ($this.UseLatest) { - $result = Repair-WinGet -Latest - } - elseif ($this.LatestPreRelease) + $hashArgs.Add("Latest", $true) + } elseif ($this.UseLatestPreRelease) { - $result = Repair-WinGet -Latest -IncludePreRelease - } - else + $hashArgs.Add("Latest", $true) + $hashArgs.Add("IncludePreRelease", $true) + } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) { - $result = Repair-WinGet -Version $this.Version + $hashArgs.Add("Version", $this.Version) } - + + $result = Repair-WinGetPackageManager @hashArgs + if ($result -ne 0) { - throw "Failed to repair winget." + throw "Failed to repair winget. Result $result" } } } diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 index d5500aaec9..956b55f521 100644 --- a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 +++ b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -25,7 +25,10 @@ param ( [Parameter(Mandatory)] [string] - $Configuration + $Configuration, + + [switch] + $SkipImportModule ) class WinGetModule @@ -96,8 +99,11 @@ if (-not $env:PSModulePath.Contains($moduleRootOutput)) } # Now import modules. -foreach($module in $modules) +if (-not $SkipImportModule) { - Write-Host "Importing module $($module.Name)" -ForegroundColor Green - Import-Module $module.Name -Force + foreach($module in $modules) + { + Write-Host "Importing module $($module.Name)" -ForegroundColor Green + Import-Module $module.Name -Force + } } diff --git a/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 index 21f6556ae9..931687d083 100644 --- a/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 @@ -4,9 +4,8 @@ <# .SYNOPSIS Simple sample on how to use WinGetAdminSettings DSC resource. - Requires PSDesiredStateConfiguration v2 and enabling the - PSDesiredStateConfiguration.InvokeDscResource experimental feature - `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` + Requires PSDesiredStateConfiguration version 2.0.6 + IMPORTANT: This will leave LocalManifestFiles enabled Run as admin for set. #> diff --git a/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 similarity index 83% rename from src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 rename to src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 index 4efcdaf78a..db679e989b 100644 --- a/src/PowerShell/scripts/samples/WinGetIntegrityResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 @@ -4,9 +4,7 @@ <# .SYNOPSIS Simple sample on how to use WinGetIntegrity DSC resource. - Requires PSDesiredStateConfiguration v2 and enabling the - PSDesiredStateConfiguration.InvokeDscResource experimental feature - `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` + Requires PSDesiredStateConfiguration version 2.0.6 IMPORTANT: This will modify the winget you have installed #> @@ -16,7 +14,7 @@ using module Microsoft.WinGet.DSC $resource = @{ - Name = 'WinGetIntegrityResource' + Name = 'WinGetPackageManager' ModuleName = 'Microsoft.WinGet.DSC' Property = @{ } @@ -47,7 +45,7 @@ else # At the time I'm doing this the second latest released winget version is v1.3.2091. Lets assume you want to stay there forever. $v132091 = "v1.3.2091" $resource = @{ - Name = 'WinGetIntegrityResource' + Name = 'WinGetPackageManager' ModuleName = 'Microsoft.WinGet.DSC' Property = @{ Version = $v132091 @@ -80,12 +78,12 @@ else } } -# Now, lets say that you want to have always the latest winget installed. You can specify the Latest. +# Now, lets say that you want to have always the latest winget installed. You can specify UseLatest. $resource = @{ - Name = 'WinGetIntegrityResource' + Name = 'WinGetPackageManager' ModuleName = 'Microsoft.WinGet.DSC' Property = @{ - Latest = $true + UseLatest = $true } } $testResult = Invoke-DscResource @resource -Method Test @@ -98,12 +96,12 @@ else Write-Host "winget version is not latest version" } -# You can also do LatestPreRelease +# You can also do UseLatestPreRelease $resource = @{ - Name = 'WinGetIntegrityResource' + Name = 'WinGetPackageManager' ModuleName = 'Microsoft.WinGet.DSC' Property = @{ - LatestPreRelease = $true + UseLatestPreRelease = $true } } $testResult = Invoke-DscResource @resource -Method Test diff --git a/src/PowerShell/scripts/samples/WinGetSourcesResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetSourcesSample.ps1 similarity index 88% rename from src/PowerShell/scripts/samples/WinGetSourcesResourceSample.ps1 rename to src/PowerShell/scripts/samples/WinGetSourcesSample.ps1 index 16460a7fd4..3148671266 100644 --- a/src/PowerShell/scripts/samples/WinGetSourcesResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetSourcesSample.ps1 @@ -4,9 +4,8 @@ <# .SYNOPSIS Simple sample on how to use WinGetSourcesResource DSC resource. - Requires PSDesiredStateConfiguration v2 and enabling the - PSDesiredStateConfiguration.InvokeDscResource experimental feature - `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` + Requires PSDesiredStateConfiguration version 2.0.6 + IMPORTANT: This deletes the main winget source and add it again. Run as admin for set. #> diff --git a/src/PowerShell/scripts/samples/WinGetUserSettingsResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 similarity index 83% rename from src/PowerShell/scripts/samples/WinGetUserSettingsResourceSample.ps1 rename to src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 index daebd41549..1a169e2ae5 100644 --- a/src/PowerShell/scripts/samples/WinGetUserSettingsResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 @@ -4,9 +4,7 @@ <# .SYNOPSIS Simple sample on how to use WinGetUserSettings DSC resource. - Requires PSDesiredStateConfiguration v2 and enabling the - PSDesiredStateConfiguration.InvokeDscResource experimental feature - `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` + Requires PSDesiredStateConfiguration version 2.0.6 IMPORTANT: If you loaded the released modules this will modify your settings. Use the -Restore to get back to your original settings @@ -27,7 +25,7 @@ param ( ) $resource = @{ - Name = 'WinGetUserSettingsResource' + Name = 'WinGetUserSettings' ModuleName = 'Microsoft.WinGet.DSC' Property = @{ } From 30c6af1f7a86da8fa5a34daa05884495a4d52ca0 Mon Sep 17 00:00:00 2001 From: Ruben Guerrero Samaniego Date: Tue, 21 Mar 2023 13:27:35 -0700 Subject: [PATCH 11/11] pr comment --- .../Helpers/AppxModuleHelper.cs | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs index a7043a0aa4..43bfe45bb0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -181,49 +181,21 @@ private IReadOnlyList GetVCLibsDependencies() throw new PSNotSupportedException(arch.ToString()); } } + else + { + this.psCmdlet.WriteDebug($"VCLibs are updated."); + } return vcLibsDependencies; } private void InstallVCLibsDependencies() { - var vcLibsPackageObjs = this.psCmdlet.InvokeCommand - .InvokeScript(string.Format(GetAppxPackageByVersionCommand, VCLibsUWPDesktop, VCLibsUWPDesktopVersion)); - if (vcLibsPackageObjs is null || - vcLibsPackageObjs.Count == 0) - { - var packages = new List(); - - var arch = RuntimeInformation.OSArchitecture; - if (arch == Architecture.X64) - { - packages.Add(VCLibsUWPDesktopX64); - } - else if (arch == Architecture.X86) - { - packages.Add(VCLibsUWPDesktopX86); - } - else if (arch == Architecture.Arm64) - { - packages.Add(VCLibsUWPDesktopX64); - packages.Add(VCLibsUWPDesktopX86); - packages.Add(VCLibsUWPDesktopArm); - packages.Add(VCLibsUWPDesktopArm64); - } - else - { - throw new PSNotSupportedException(arch.ToString()); - } - - foreach (var package in packages) - { - this.psCmdlet.WriteDebug($"Installing VCLibs {package}"); - this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, package)); - } - } - else + var packages = this.GetVCLibsDependencies(); + foreach (var package in packages) { - this.psCmdlet.WriteDebug($"VCLibs are updated."); + this.psCmdlet.WriteDebug($"Installing VCLibs {package}"); + this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, package)); } }