commit 4aab7f7e9826af135807ea965167b8990c90a7a9 parent 578f451ebca68f46e0d11271cde5df7bc5aa29c6 Author: Ruben Guerrero <rubengu@microsoft.com> Date: Thu, 20 Apr 2023 09:57:42 -0700 Microsoft.WinGet.Client custom assembly load context (#3150) This PR modifies the Microsoft.WinGet.Client module to be more dependency friendly and play nice with winget configure. It uses a custom AssemblyLoadContext that handles loading all the assembly dependencies of this module. For this, the Microsoft.WinGet.Client.Engine project was created. This project wraps all the cmdlet functionality on it and leaves the Microsoft.WinGet.Client.Cmdlets.dll (formerly Microsoft.WinGet.Client.dll) to just define the visibility of the cmdlets. The custom ALC is registered at onImport module initializer and cleaned up when the module is removed. To avoid loading any other dlls at import module time, none of the cswinrt objects from Microsoft.Management.Deployment are exposed as parameters or outputs of the cmdlets. The engine contains PS wrappers objects and enum that are converted internally by and only by the engine. Other fixes: Removed the dependency on the PowerShellSDK for the PowerShell 7 version of the binary. It was used to call an API to convert json into hashtable but this wouldn't work for Windows PowerShell. Instead, I just took what PowerShell 7 does and applied it to both. We still don't support Windows PowerShell, but is a step forward into doing it. Fixed passing a catalog package object back to the cmdlets to avoid the search. It got broken when Find-WinGetPackage and Get-WinGetPackage started returning an object instead of the cswinrt CatalogPackage object. The wrapper contains the catalog package but is not accessible to the user, so there was no way to pass it as a parameter back to Install-WinGetPackage, Modified the PowerShell output directory for binary to follow more what nuget suggests for platform specific binaries. Diffstat:
140 files changed, 6336 insertions(+), 5167 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -86,6 +86,7 @@ debian deigh deleteifnotneeded desktopappinstaller +dic diskfull dismapi dllimport @@ -171,6 +172,7 @@ ISVs itr IWin JArray +JDictionary jdk jfearn JObject @@ -239,6 +241,7 @@ nativehandle NESTEDINSTALLER netfx netlify +NETSDK Newtonsoft NOEXPAND NOLINKINFO diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -131,7 +131,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CertificateResources", "Cer EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerShell", "PowerShell", "{7C218A3E-9BC8-48FF-B91B-BCACD828C0C9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client", "PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.csproj", "{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client.Cmdlets", "PowerShell\Microsoft.WinGet.Client.Cmdlets\Microsoft.WinGet.Client.Cmdlets.csproj", "{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Deployment.Projection", "Microsoft.Management.Deployment.Projection\Microsoft.Management.Deployment.Projection.csproj", "{0B104762-5CD8-47EE-A904-71C1C3F84DCD}" EndProject @@ -161,6 +161,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Config EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationRemotingServer", "ConfigurationRemotingServer\ConfigurationRemotingServer.csproj", "{6597EB04-D105-49A7-A5A3-D27FE1DF895E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client.Engine", "PowerShell\Microsoft.WinGet.Client.Engine\Microsoft.WinGet.Client.Engine.csproj", "{1F56BECB-D65D-4BBA-8788-6671B251392A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -874,6 +876,30 @@ Global {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x64.Build.0 = Release|x64 {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x86.ActiveCfg = Release|x86 {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x86.Build.0 = Release|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|ARM64.Build.0 = Debug|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x64.ActiveCfg = Debug|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x64.Build.0 = Debug|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x86.ActiveCfg = Debug|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x86.Build.0 = Debug|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|ARM64.Build.0 = Debug|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x64.ActiveCfg = Debug|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x64.Build.0 = Debug|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x86.ActiveCfg = Debug|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x86.Build.0 = Debug|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|ARM64.ActiveCfg = Release|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|ARM64.Build.0 = Release|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x64.ActiveCfg = Release|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x64.Build.0 = Release|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x86.ActiveCfg = Release|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x86.Build.0 = Release|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.TestRelease|ARM64.ActiveCfg = Release|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.TestRelease|ARM64.Build.0 = Release|ARM64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.TestRelease|x64.ActiveCfg = Release|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.TestRelease|x64.Build.0 = Release|x64 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.TestRelease|x86.ActiveCfg = Release|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.TestRelease|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -899,6 +925,7 @@ Global {787EC629-C0FB-4BA9-9746-4A82CD06B73E} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {8E43F982-40D5-4DF1-9044-C08047B5F43B} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} {BB14D603-F44E-4415-8770-BF3E13F4C17F} = {60618CAC-2995-4DF9-9914-45C6FC02C995} + {1F56BECB-D65D-4BBA-8788-6671B251392A} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/AppInstallerCLIE2ETests/PowerShell/WinGetClientModule.cs b/src/AppInstallerCLIE2ETests/PowerShell/WinGetClientModule.cs @@ -90,85 +90,37 @@ namespace AppInstallerCLIE2ETests.PowerShell } /// <summary> + /// Tests WinGetPackage cmdlets. /// Find-WinGetPackage. + /// Install-WinGetPackage. + /// Get-WinGetPackage. + /// Update-WinGetPackage. + /// Uninstall-WinGetPackage. /// </summary> [Test] - public void FindWinGetPackage() + public void WinGetPackageCmdlets() { if (!Environment.Is64BitProcess) { return; } + // TODO: It would be nice to add an installer to be use ONLY by the PowerShell cmdlets for E2E to avoid conflicts and bad + // cleanups in other tests. var result = TestCommon.RunPowerShellCoreCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}; {result.StdErr}"); Assert.IsTrue(result.StdOut.Contains("TestExeInstaller")); - } - /// <summary> - /// Tests Get-WinGetPackage. - /// </summary> - [Test] - public void GetWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } + var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"Failed with the following output: {installResult.StdOut}"); - var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); - var getResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); - var uninstallResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + var updateResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, updateResult.ExitCode, $"Failed with the following output: {updateResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); + var getResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); - - Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); - Assert.IsTrue(getResult.StdOut.Contains("TestMsiInstaller")); - Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); - } - - /// <summary> - /// Tests Install-WinGetPackage. - /// </summary> - [Test] - public void InstallWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } - - var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - var uninstallResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); - - Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); - Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); - } - - /// <summary> - /// Tests Update-WinGetPackage. - /// </summary> - [Test] - public void UpdateWinGetPackage() - { - if (!Environment.Is64BitProcess) - { - return; - } - var installResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); - var updateResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - var getResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); var uninstallResult = TestCommon.RunPowerShellCoreCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"Failed with the following output: {installResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, updateResult.ExitCode, $"Failed with the following output: {updateResult.StdOut}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); @@ -260,7 +212,7 @@ namespace AppInstallerCLIE2ETests.PowerShell // trying to load the runspace. This is most probably because the same dll is already loaded. // Check the type the long way. dynamic exception = cmdletException.InnerException; - Assert.AreEqual(exception.GetType().ToString(), "Microsoft.WinGet.Client.Exceptions.UserSettingsReadException"); + Assert.AreEqual(exception.GetType().ToString(), "Microsoft.WinGet.Client.Engine.Exceptions.UserSettingsReadException"); } /// <summary> @@ -1124,7 +1076,7 @@ namespace AppInstallerCLIE2ETests.PowerShell // trying to load the runspace. This is most probably because the same dll is already loaded. // Check the type the long way. dynamic exception = cmdletException.InnerException; - Assert.AreEqual(exception.GetType().ToString(), "Microsoft.WinGet.Client.Exceptions.UserSettingsReadException"); + Assert.AreEqual(exception.GetType().ToString(), "Microsoft.WinGet.Client.Engine.Exceptions.UserSettingsReadException"); } private bool IsRunning(string processName) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/ModuleInit.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/ModuleInit.cs @@ -0,0 +1,30 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ModuleInit.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- +#if !POWERSHELL_WINDOWS +namespace Microsoft.WinGet.Client.Acl +{ + using System.Management.Automation; + using System.Runtime.Loader; + + /// <summary> + /// Initialization class for this module. + /// </summary> + public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup + { + /// <inheritdoc/> + public void OnImport() + { + AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; + } + + /// <inheritdoc/> + public void OnRemove(PSModuleInfo module) + { + AssemblyLoadContext.Default.Resolving -= WinGetAssemblyLoadContext.ResolvingHandler; + } + } +} +#endif+ \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Acl/WinGetAssemblyLoadContext.cs @@ -0,0 +1,84 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetAssemblyLoadContext.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- +#if !POWERSHELL_WINDOWS +namespace Microsoft.WinGet.Client.Acl +{ + using System; + using System.IO; + using System.Reflection; + using System.Runtime.Loader; + + /// <summary> + /// Custom assembly load context for this module. + /// This helps us load our dependencies without carrying about apps importing this module. + /// All dependencies except the Engine dll needs to be under a Dependencies directory. + /// </summary> + internal class WinGetAssemblyLoadContext : AssemblyLoadContext + { + private static readonly string SharedDependencyPath = Path.Combine( + Path.GetDirectoryName(typeof(WinGetAssemblyLoadContext).Assembly.Location), + "SharedDependencies"); + + private static readonly string DirectDependencyPath = Path.Combine( + Path.GetDirectoryName(typeof(WinGetAssemblyLoadContext).Assembly.Location), + "DirectDependencies"); + + private static readonly WinGetAssemblyLoadContext WinGetAcl = new (); + + private WinGetAssemblyLoadContext() + : base("WinGetAssemblyLoadContext", isCollectible: false) + { + } + + /// <summary> + /// Handler to resolve assemblies. + /// </summary> + /// <param name="context">Assembly load context.</param> + /// <param name="assemblyName">Assembly name.</param> + /// <returns>The assembly, null if not in our assembly location.</returns> + internal static Assembly ResolvingHandler(AssemblyLoadContext context, AssemblyName assemblyName) + { + string path = $"{Path.Combine(DirectDependencyPath, assemblyName.Name)}.dll"; + if (File.Exists(path)) + { + return WinGetAcl.LoadFromAssemblyName(assemblyName); + } + + return null; + } + + /// <inheritdoc/> + protected override Assembly Load(AssemblyName assemblyName) + { + string path = $"{Path.Combine(SharedDependencyPath, assemblyName.Name)}.dll"; + if (File.Exists(path)) + { + return this.LoadFromAssemblyPath(path); + } + + path = $"{Path.Combine(DirectDependencyPath, assemblyName.Name)}.dll"; + if (File.Exists(path)) + { + return this.LoadFromAssemblyPath(path); + } + + return null; + } + + /// <inheritdoc/> + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string path = Path.Combine(SharedDependencyPath, unmanagedDllName); + if (File.Exists(path)) + { + return this.LoadUnmanagedDllFromPath(path); + } + + return IntPtr.Zero; + } + } +} +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------------- +// <copyright file="AssertWinGetPackageManagerCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// <summary> + /// Assert-WinGetPackageManager. Verifies winget is installed properly. + /// </summary> + [Cmdlet( + VerbsLifecycle.Assert, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + /// <summary> + /// Validates winget is installed correctly. If not, throws an exception + /// with the reason why, if any. + /// </summary> + protected override void ProcessRecord() + { + var command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + command.AssertUsingLatest(this.IncludePreRelease.ToBool()); + } + else + { + command.Assert(this.Version); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderCmdlet.cs @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FinderCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + + /// <summary> + /// This is the base class for all commands that might need to search for a package. It contains an initial + /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. + /// </summary> + public abstract class FinderCmdlet : PSCmdlet + { + /// <summary> + /// Gets or sets the field that is matched against the identifier of a package. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Id { get; set; } + + /// <summary> + /// Gets or sets the field that is matched against the name of a package. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// <summary> + /// Gets or sets the field that is matched against the moniker of a package. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Moniker { get; set; } + + /// <summary> + /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Source { get; set; } + + /// <summary> + /// Gets or sets the strings that match against every field of a package. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + Position = 0, + ValueFromPipelineByPropertyName = true, + ValueFromRemainingArguments = true)] + public string[] Query { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to match exactly against package fields. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter Exact { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderExtendedCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderExtendedCmdlet.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FinderExtendedCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + + /// <summary> + /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., + /// the "search" and "list" commands. This class contains an extended set of parameters suited for + /// that purpose. + /// </summary> + public abstract class FinderExtendedCmdlet : FinderCmdlet + { + /// <summary> + /// Gets or sets the filter that is matched against the tags of the package. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Tag { get; set; } + + /// <summary> + /// Gets or sets the filter that is matched against the commands of the package. + /// </summary> + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Command { get; set; } + + /// <summary> + /// Gets or sets the maximum number of results returned. + /// </summary> + [ValidateRange(Constants.CountLowerBound, Constants.CountUpperBound)] + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public uint Count { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InstallCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.IO; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// This is the base class for all commands that parse a FindPackagesOptions result + /// from the provided parameters i.e., the "install" and "upgrade" commands. + /// </summary> + public abstract class InstallCmdlet : PackageCmdlet + { + private string location; + + /// <summary> + /// Gets or sets the mode to manipulate the package with. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageInstallMode Mode { get; set; } = PSPackageInstallMode.Default; + + /// <summary> + /// Gets or sets the override arguments to be passed on to the installer. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Override { get; set; } + + /// <summary> + /// Gets or sets the arguments to be passed on to the installer in addition to the defaults. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Custom { get; set; } + + /// <summary> + /// Gets or sets the installation location. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Location + { + get => this.location; + set + { + this.location = Path.IsPathRooted(value) + ? value + : this.SessionState.Path.CurrentFileSystemLocation + @"\" + value; + } + } + + /// <summary> + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllowHashMismatch { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// <summary> + /// Gets or sets the optional HTTP Header to pass on to the REST Source. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Header { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PackageCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// This is the base class for commands which operate on a specific package and version i.e., + /// the "install", "uninstall", and "upgrade" commands. + /// </summary> + public abstract class PackageCmdlet : FinderCmdlet + { + /// <summary> + /// Gets or sets the package to directly install. + /// </summary> + [Alias("InputObject")] + [ValidateNotNull] + [Parameter( + ParameterSetName = Constants.GivenSet, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSCatalogPackage PSCatalogPackage { get; set; } = null; + + /// <summary> + /// Gets or sets the version to install. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } + + /// <summary> + /// Gets or sets the path to the logging file. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Log { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetPackageManagerCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + + /// <summary> + /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. + /// </summary> + public abstract class WinGetPackageManagerCmdlet : PSCmdlet + { + /// <summary> + /// Gets or sets the optional version. + /// </summary> + [Parameter( + ParameterSetName = Constants.IntegrityVersionSet, + ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets a value indicating whether to use latest. + /// </summary> + [Parameter( + ParameterSetName = Constants.IntegrityLatestSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter Latest { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to include prerelease winget versions. + /// </summary> + [Parameter( + ParameterSetName = Constants.IntegrityLatestSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludePreRelease { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/FindPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/FindPackageCmdlet.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FindPackageCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Searches configured sources for packages. + /// </summary> + [Cmdlet(VerbsCommon.Find, Constants.WinGetNouns.Package)] + [OutputType(typeof(PSFoundCatalogPackage))] + public sealed class FindPackageCmdlet : FinderExtendedCmdlet + { + /// <summary> + /// Searches for configured sources for packages. + /// </summary> + protected override void ProcessRecord() + { + var command = new FinderPackageCommand( + this, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.Exact.ToBool(), + this.Tag, + this.Command, + this.Count); + + command.Find(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetPackageCmdlet.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------------- +// <copyright file="GetPackageCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Searches configured sources for packages. + /// </summary> + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Package)] + [OutputType(typeof(PSInstalledCatalogPackage))] + public sealed class GetPackageCmdlet : FinderExtendedCmdlet + { + /// <summary> + /// Searches for configured sources for packages. + /// </summary> + protected override void ProcessRecord() + { + var command = new FinderPackageCommand( + this, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.Exact.ToBool(), + this.Tag, + this.Command, + this.Count); + + command.Get(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetSourceCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetSourceCmdlet.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------------- +// <copyright file="GetSourceCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Retrieves the list of configured sources. + /// </summary> + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Source)] + [OutputType(typeof(PSSourceResult))] + public sealed class GetSourceCmdlet : PSCmdlet + { + /// <summary> + /// Gets or sets the name of the source to retrieve. + /// </summary> + [Parameter( + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// <summary> + /// Returns the list of configured sources. + /// </summary> + protected override void ProcessRecord() + { + var command = new SourceCommand(this); + command.Get(this.Name); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetUserSettingsCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetUserSettingsCmdlet.cs @@ -0,0 +1,30 @@ +// ----------------------------------------------------------------------------- +// <copyright file="GetUserSettingsCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Collections; + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// <summary> + /// Gets winget's user settings. + /// </summary> + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.UserSettings)] + [OutputType(typeof(Hashtable))] + public sealed class GetUserSettingsCmdlet : PSCmdlet + { + /// <summary> + /// Writes the settings file contents. + /// </summary> + protected override void ProcessRecord() + { + var command = new UserSettingsCommand(this); + command.Get(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetVersionCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetVersionCmdlet.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------------- +// <copyright file="GetVersionCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// <summary> + /// Get-WinGetVersion. Gets the current version of winget. + /// </summary> + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] + [OutputType(typeof(string))] + public class GetVersionCmdlet : PSCmdlet + { + /// <summary> + /// Writes the winget version. + /// </summary> + protected override void ProcessRecord() + { + var command = new VersionCommand(this); + command.Get(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InstallPackageCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Installs a package from the pipeline or from a configured source. + /// </summary> + [Cmdlet( + VerbsLifecycle.Install, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSInstallResult))] + public sealed class InstallPackageCmdlet : InstallCmdlet + { + /// <summary> + /// Gets or sets the scope to install the application under. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageInstallScope Scope { get; set; } = PSPackageInstallScope.Any; + + /// <summary> + /// Gets or sets the architecture of the application to be installed. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSProcessorArchitecture Architecture { get; set; } = PSProcessorArchitecture.Default; + + /// <summary> + /// Installs a package from the pipeline or from a configured source. + /// </summary> + protected override void ProcessRecord() + { + var command = new InstallerPackageCommand( + this, + this.Mode.ToString(), + this.Override, + this.Custom, + this.Location, + this.AllowHashMismatch.ToBool(), + this.Force.ToBool(), + this.Header, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.Exact.ToBool()); + command.Install(this.Scope.ToString(), this.Architecture.ToString()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// <copyright file="RepairWinGetPackageManagerCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// <summary> + /// Repair-WinGetPackageManager. Repairs winget if needed. + /// </summary> + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [OutputType(typeof(int))] + public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + /// <summary> + /// Attempts to repair winget. + /// TODO: consider WhatIf and Confirm options. + /// </summary> + protected override void ProcessRecord() + { + var command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + command.RepairUsingLatest(this.IncludePreRelease.ToBool()); + } + else + { + command.Repair(this.Version); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/SetUserSettingsCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/SetUserSettingsCmdlet.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------------- +// <copyright file="SetUserSettingsCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Collections; + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// <summary> + /// Sets the specified user settings into the winget user settings. If the merge switch is on, merges current user + /// settings with the input settings. Otherwise, overwrites the input settings. + /// </summary> + [Cmdlet(VerbsCommon.Set, Constants.WinGetNouns.UserSettings)] + [OutputType(typeof(Hashtable))] + public sealed class SetUserSettingsCmdlet : PSCmdlet + { + /// <summary> + /// Gets or sets the input user settings. + /// </summary> + [Parameter( + Mandatory = true, + ValueFromPipelineByPropertyName = true)] + public Hashtable UserSettings { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to merge the current user settings and the input settings. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Merge { get; set; } + + /// <summary> + /// Process input of cmdlet. + /// </summary> + protected override void ProcessRecord() + { + var command = new UserSettingsCommand(this); + command.Set(this.UserSettings, this.Merge.ToBool()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/TestUserSettingsCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/TestUserSettingsCmdlet.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------------- +// <copyright file="TestUserSettingsCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Collections; + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// <summary> + /// Compare the specified user settings with the winget user settings. + /// </summary> + [Cmdlet(VerbsDiagnostic.Test, Constants.WinGetNouns.UserSettings)] + [OutputType(typeof(bool))] + public sealed class TestUserSettingsCmdlet : PSCmdlet + { + /// <summary> + /// Gets or sets the input user settings. + /// </summary> + [Parameter( + Mandatory = true, + ValueFromPipelineByPropertyName = true)] + public Hashtable UserSettings { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to ignore comparing settings that are not part of the input. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter IgnoreNotSet { get; set; } + + /// <summary> + /// Process the cmdlet and writes the result of the comparison. + /// </summary> + protected override void ProcessRecord() + { + var command = new UserSettingsCommand(this); + command.Test(this.UserSettings, this.IgnoreNotSet.ToBool()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs @@ -0,0 +1,57 @@ +// ----------------------------------------------------------------------------- +// <copyright file="UninstallPackageCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Uninstalls a package from the local system. + /// </summary> + [Cmdlet( + VerbsLifecycle.Uninstall, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSUninstallResult))] + public sealed class UninstallPackageCmdlet : PackageCmdlet + { + /// <summary> + /// Gets or sets the desired mode for the uninstallation process. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageUninstallMode Mode { get; set; } = PSPackageUninstallMode.Default; + + /// <summary> + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// <summary> + /// Uninstalls a package from the local system. + /// </summary> + protected override void ProcessRecord() + { + var command = new UninstallPackageCommand( + this, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.Exact.ToBool()); + command.Uninstall(this.Mode.ToString(), this.Force.ToBool()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------------- +// <copyright file="UpdatePackageCmdlet.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// This commands updates a package from the pipeline or from the local system. + /// </summary> + [Cmdlet( + VerbsData.Update, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSInstallResult))] + public sealed class UpdatePackageCmdlet : InstallCmdlet + { + /// <summary> + /// Gets or sets a value indicating whether updating to an unknown version is allowed. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludeUnknown { get; set; } + + /// <summary> + /// Updates a package from the pipeline or from the local system. + /// </summary> + protected override void ProcessRecord() + { + var command = new InstallerPackageCommand( + this, + this.Mode.ToString(), + this.Override, + this.Custom, + this.Location, + this.AllowHashMismatch.ToBool(), + this.Force.ToBool(), + this.Header, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.Exact.ToBool()); + command.Update(this.IncludeUnknown.ToBool()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs @@ -0,0 +1,76 @@ +// ----------------------------------------------------------------------------- +// <copyright file="Constants.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Common +{ + /// <summary> + /// This class contains all of the configurable constants for this project. + /// </summary> + internal static class Constants + { + /// <summary> + /// If a command allows the specification of the maximum number of results to return, this is the lower bound for that value. + /// </summary> + public const uint CountLowerBound = 1; + + /// <summary> + /// If a command allows the specification of the maximum number of results to return, this is the upper bound for that value. + /// </summary> + public const uint CountUpperBound = 1000; + + /// <summary> + /// This parameter set indicates that a package was provided via a parameter or the pipeline and it can be acted on directly. + /// </summary> + public const string GivenSet = "GivenSet"; + + /// <summary> + /// This parameter set indicates that a package was not provided via a parameter or the pipeline and it + /// needs to be found by searching a package source. + /// </summary> + public const string FoundSet = "FoundSet"; + + /// <summary> + /// Parameter set for an specific version parameter. + /// </summary> + public const string IntegrityVersionSet = "IntegrityVersionSet"; + + /// <summary> + /// Parameter set for an latest version with optional prerelease version. + /// </summary> + public const string IntegrityLatestSet = "IntegrityLatestSet"; + + /// <summary> + /// Nouns used for different cmdlets. Changing this will alter the names of the related commands. + /// </summary> + public static class WinGetNouns + { + /// <summary> + /// WinGet. + /// </summary> + public const string WinGetPackageManager = "WinGetPackageManager"; + + /// <summary> + /// WinGetPackage. + /// </summary> + public const string Package = "WinGetPackage"; + + /// <summary> + /// WinGetSource. + /// </summary> + public const string Source = "WinGetSource"; + + /// <summary> + /// The noun for any user settings cmdlet. + /// </summary> + public const string UserSettings = "WinGetUserSettings"; + + /// <summary> + /// The noun for winget version. + /// </summary> + public const string Version = "WinGetVersion"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Microsoft.WinGet.Client.Cmdlets.csproj @@ -0,0 +1,147 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <!-- If these target frameworks are updated, make sure to also update the .psd1 and .nuspec files.--> + <CoreFramework>net6.0-windows10.0.22000.0</CoreFramework> + <DesktopFramework>net48</DesktopFramework> + </PropertyGroup> + + <PropertyGroup> + <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> + <LangVersion>10</LangVersion> + <BuildOutputDirectory>$(SolutionDir)$(Platform)\$(Configuration)\</BuildOutputDirectory> + <OutputPath>$(BuildOutputDirectory)$(MSBuildProjectName)</OutputPath> + <Platforms>x64;x86;ARM64</Platforms> + <TargetFrameworks>$(CoreFramework);$(DesktopFramework)</TargetFrameworks> + <DocumentationFile>$(OutputPath)\Microsoft.WinGet.Client.Cmdlets.xml</DocumentationFile> + </PropertyGroup> + + <ItemGroup> + <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> + </ItemGroup> + + <PropertyGroup Condition="'$(Configuration)'=='Release'"> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="all" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + + <PropertyGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> + <DefineConstants>POWERSHELL_WINDOWS</DefineConstants> + </PropertyGroup> + + <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifiers>win10-x64;win10-x86;win10-arm;win10-arm64</RuntimeIdentifiers> + </PropertyGroup> + + <!-- This project doesn't reference it directly, but if I don't add it here it will fail with NETSDK1130 *.winmd cannot be referenced. --> + <ItemGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.5" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Microsoft.WinGet.Client.Engine\Microsoft.WinGet.Client.Engine.csproj" /> + </ItemGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'x64' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-x64</RuntimeIdentifier> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'x86' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-x86</RuntimeIdentifier> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'arm' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-arm</RuntimeIdentifier> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'arm64' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-arm64</RuntimeIdentifier> + </PropertyGroup> + + <!-- Build PowerShell Module --> + <PropertyGroup> + <PowerShellModuleOutputDirectory>$(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Client</PowerShellModuleOutputDirectory> + <PowerShellModuleRuntimesDir>$(PowerShellModuleOutputDirectory)\runtimes\win10-$(Platform)\lib\$(TargetFramework)</PowerShellModuleRuntimesDir> + <PowerShellModuleSharedDependencies>$(PowerShellModuleRuntimesDir)\SharedDependencies</PowerShellModuleSharedDependencies> + <PowerShellModuleDirectDependencies>$(PowerShellModuleRuntimesDir)\DirectDependencies</PowerShellModuleDirectDependencies> + </PropertyGroup> + + <!-- psm1, psd1 and ps1xml--> + <Target Name="CopyModuleFiles" AfterTargets="AfterBuild"> + <ItemGroup> + <ModuleFiles Include="$(ProjectDir)\..\Microsoft.WinGet.Client\ModuleFiles\**\*.*" /> + </ItemGroup> + <Message Importance="high" Text="Copying PowerShell module files to '$(PowerShellModuleOutputDirectory)'" /> + <Copy SourceFiles="@(ModuleFiles)" DestinationFiles="$(PowerShellModuleOutputDirectory)\%(RecursiveDir)%(Filename)%(Extension)" /> + </Target> + + <!-- Main binaries --> + <Target Name="CopyWinGetBinaries" AfterTargets="AfterBuild" Condition="'$(Platform)' != 'ARM'"> + <ItemGroup> + <ModuleWinGetFile Include="$(OutputPath)Microsoft.WinGet.Client.Cmdlets.dll" /> + <ModuleWinGetFile Include="$(OutputPath)Microsoft.WinGet.Client.Cmdlets.pdb" /> + </ItemGroup> + <Message Importance="high" Text="Copying CopyWinGetBinaries: '@(ModuleWinGetFile)'" /> + <Copy SourceFiles="@(ModuleWinGetFile)" DestinationFolder="$(PowerShellModuleRuntimesDir)" /> + </Target> + + <!-- Direct dependencies --> + <Target Name="CopyDirectDependencies" AfterTargets="AfterBuild" Condition="'$(Platform)' != 'ARM'"> + <ItemGroup> + <ModuleDirectDependency Include="$(OutputPath)Microsoft.WinGet.Client.Engine.dll" /> + <ModuleDirectDependency Include="$(OutputPath)Microsoft.WinGet.Client.Engine.pdb" /> + </ItemGroup> + <Message Importance="high" Text="Copying direct dependencies: '@(ModuleDirectDependency)'" /> + <Copy SourceFiles="@(ModuleDirectDependency)" DestinationFolder="$(PowerShellModuleDirectDependencies)" /> + </Target> + + <!-- Shared dependencies --> + <Target Name="CopySharedDependencies" AfterTargets="AfterBuild" Condition="'$(Platform)' != 'ARM'"> + <ItemGroup> + <ModuleSharedDependency Include="$(OutputPath)Microsoft.Windows.SDK.NET.dll" Condition="'$(TargetFramework)' == '$(CoreFramework)'" /> + <ModuleSharedDependency Include="$(OutputPath)Newtonsoft.Json.dll" /> + <ModuleSharedDependency Include="$(OutputPath)Octokit.dll" /> + <ModuleSharedDependency Include="$(OutputPath)WinRT.Runtime.dll" Condition="'$(TargetFramework)' == '$(CoreFramework)'" /> + </ItemGroup> + <Message Importance="high" Text="Copying shared dependencies: '@(ModuleSharedDependency)'" /> + <Copy SourceFiles="@(ModuleSharedDependency)" DestinationFolder="$(PowerShellModuleSharedDependencies)" /> + </Target> + + <!-- Dependencies of Engine that doesn't get copied in the output directory --> + <Target Name="CopyEngineDependencies" AfterTargets="AfterBuild" Condition="'$(Platform)' != 'ARM'"> + <PropertyGroup> + <EngineOutDir>$(OutputPath)..\..\..\Microsoft.WinGet.Client.Engine\$(TargetFramework)\win10-$(Platform)\</EngineOutDir> + <!-- The output path for this project is different on some builds where the RIDs are not used in the output. --> + <EngineOutDir Condition="!Exists('$(EngineOutDir)Microsoft.WinGet.Client.Engine.dll')">$(OutputPath)..\..\..\Microsoft.WinGet.Client.Engine\$(TargetFramework)\</EngineOutDir> + <EngineOutDir Condition="!Exists('$(EngineOutDir)Microsoft.WinGet.Client.Engine.dll')">$(OutputPath)..\..\Microsoft.WinGet.Client.Engine\$(TargetFramework)\</EngineOutDir> + </PropertyGroup> + <ItemGroup> + <ModuleEngineDependency Include="$(EngineOutDir)Microsoft.Management.Deployment.InProc.dll" /> + <ModuleEngineDependency Include="$(EngineOutDir)Microsoft.Management.Deployment.winmd" /> + <ModuleEngineDependency Include="$(EngineOutDir)WindowsPackageManager.dll" /> + <ModuleEngineDependency Include="$(EngineOutDir)winrtact.dll" /> + </ItemGroup> + <Message Importance="high" Text="Copying CopyEngineDependencies: '@(ModuleEngineDependency)'" /> + <Copy SourceFiles="@(ModuleEngineDependency)" DestinationFolder="$(PowerShellModuleSharedDependencies)" /> + </Target> + + <!-- Rename Microsoft.Management.Deployment.InProc.dll to Microsoft.Management.Deployment.dll --> + <Target Name="RenameMicrosoftManagementDeployment" AfterTargets="AfterBuild"> + <ItemGroup> + <RenameModuleFiles Include="$(PowerShellModuleDirectDependencies)\**\Microsoft.Management.Deployment.InProc.dll"> + <NewName>Microsoft.Management.Deployment.dll</NewName> + </RenameModuleFiles> + </ItemGroup> + <Message Importance="high" Text="Renaming file '%(RenameModuleFiles.Identity)' to '$(PowerShellModuleLibDirectory)\%(RenameModuleFiles.RecursiveDir)\%(RenameModuleFiles.NewName)'" /> + <Move SourceFiles="%(RenameModuleFiles.Identity)" DestinationFiles="$(PowerShellModuleDirectDependencies)\%(RenameModuleFiles.RecursiveDir)\%(RenameModuleFiles.NewName)" /> + </Target> + +</Project> + diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallMode.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSPackageInstallMode.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// <summary> + /// This should mimic Microsoft.Management.Deployment.PackageInstallMode. + /// </summary> + public enum PSPackageInstallMode + { + /// <summary> + /// Default, + /// </summary> + Default, + + /// <summary> + /// Silent, + /// </summary> + Silent, + + /// <summary> + /// Interactive, + /// </summary> + Interactive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallScope.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallScope.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSPackageInstallScope.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// <summary> + /// This must match Microsoft.Management.Deployment.PackageInstallScope. + /// </summary> + public enum PSPackageInstallScope + { + /// <summary> + /// Any. + /// </summary> + Any, + + /// <summary> + /// User. + /// </summary> + User, + + /// <summary> + /// System. + /// </summary> + System, + + /// <summary> + /// User or unknown. + /// </summary> + UserOrUnknown, + + /// <summary> + /// System or unknown. + /// </summary> + SystemOrUnknown, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageUninstallMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageUninstallMode.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSPackageUninstallMode.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// <summary> + /// Must match Microsoft.Management.Deployment.PackageUninstallMode. + /// </summary> + public enum PSPackageUninstallMode + { + /// <summary> + /// Default. + /// </summary> + Default, + + /// <summary> + /// Silent. + /// </summary> + Silent, + + /// <summary> + /// Interactive. + /// </summary> + Interactive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSProcessorArchitecture.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSProcessorArchitecture.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSProcessorArchitecture.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// <summary> + /// The processor architecture of the package to install. + /// </summary> + public enum PSProcessorArchitecture + { + /// <summary> + /// Let winget decide. + /// </summary> + Default, + + /// <summary> + /// x86, + /// </summary> + X86, + + /// <summary> + /// ARM. + /// </summary> + Arm, + + /// <summary> + /// x64. + /// </summary> + X64, + + /// <summary> + /// Arm64 + /// </summary> + Arm64, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Attributes/FilterAttribute.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Attributes/FilterAttribute.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FilterAttribute.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Attributes +{ + using System; + using Microsoft.Management.Deployment; + + /// <summary> + /// A <see cref="FindPackagesOptions" /> is constructed by introspecting on the inheritance tree and + /// looking for parameters that are marked with this attribute. Properties that are marked with this + /// attribute are added to the <see cref="FindPackagesOptions" /> object. + /// </summary> + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FilterAttribute : Attribute + { + /// <summary> + /// Gets or sets the field that the filter will be matching against. + /// </summary> + public PackageMatchField Field { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/BaseCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/BaseCommand.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------------- +// <copyright file="BaseCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + + /// <summary> + /// Base class for all Cmdlets. + /// </summary> + public abstract class BaseCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="BaseCommand"/> class. + /// </summary> + /// <param name="psCmdlet">PSCmdlet.</param> + internal BaseCommand(PSCmdlet psCmdlet) + : base() + { + // The inproc COM API may deadlock on an STA thread. + if (Utilities.UsesInProcWinget && Utilities.ThreadIsSTA) + { + throw new SingleThreadedApartmentException(); + } + + this.PsCmdlet = psCmdlet; + } + + /// <summary> + /// Gets the caller PSCmdlet. + /// </summary> + protected PSCmdlet PsCmdlet { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ClientCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ClientCommand.cs @@ -0,0 +1,71 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ClientCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + + /// <summary> + /// This is the base class for all of the commands in this module that use the COM APIs. + /// </summary> + public abstract class ClientCommand : BaseCommand + { + static ClientCommand() + { + InitializeUndockedRegFreeWinRT(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="ClientCommand"/> class. + /// </summary> + /// <param name="psCmdlet">psCmdlet.</param> + internal ClientCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Gets the instance of the <see cref="ComObjectFactory" /> class. + /// </summary> + protected static Lazy<ComObjectFactory> ComObjectFactory { get; } = new (); + + /// <summary> + /// Gets the instance of the <see cref="PackageManager" /> class. + /// </summary> + protected static Lazy<PackageManager> PackageManager { get; } = new (() => ComObjectFactory.Value.CreatePackageManager()); + + /// <summary> + /// Retrieves the specified source or all sources if <paramref name="source" /> is null. + /// </summary> + /// <returns>A list of <see cref="PackageCatalogReference" /> instances.</returns> + /// <param name="source">The name of the source to retrieve. If null, then all sources are returned.</param> + /// <exception cref="ArgumentException">The source does not exist.</exception> + protected static IReadOnlyList<PackageCatalogReference> GetPackageCatalogReferences(string source) + { + if (string.IsNullOrEmpty(source)) + { + return PackageManager.Value.GetPackageCatalogs(); + } + else + { + return new List<PackageCatalogReference>() + { + PackageManager.Value.GetPackageCatalogByName(source) + ?? throw new InvalidSourceException(source), + }; + } + } + + [DllImport("winrtact.dll", EntryPoint = "winrtact_Initialize", ExactSpelling = true, PreserveSig = true)] + private static extern void InitializeUndockedRegFreeWinRT(); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderCommand.cs @@ -0,0 +1,200 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FinderCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using System.Reflection; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Attributes; + using Microsoft.WinGet.Client.Engine.Exceptions; + + /// <summary> + /// This is the base class for all commands that might need to search for a package. It contains an initial + /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. + /// </summary> + public abstract class FinderCommand : ClientCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="FinderCommand"/> class. + /// </summary> + /// <param name="psCmdlet">PSCmdlet.</param> + internal FinderCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Gets or sets the field that is matched against the identifier of a package. + /// </summary> + [Filter(Field = PackageMatchField.Id)] + protected string Id { get; set; } + + /// <summary> + /// Gets or sets the field that is matched against the name of a package. + /// </summary> + [Filter(Field = PackageMatchField.Name)] + protected string Name { get; set; } + + /// <summary> + /// Gets or sets the field that is matched against the moniker of a package. + /// </summary> + [Filter(Field = PackageMatchField.Moniker)] + protected string Moniker { get; set; } + + /// <summary> + /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. + /// </summary> + protected string Source { get; set; } + + /// <summary> + /// Gets or sets the strings that match against every field of a package. + /// </summary> + protected string[] Query { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to match exactly against package fields. + /// </summary> + protected bool Exact { get; set; } + + private string QueryAsJoinedString + { + get + { + return this.Query is null + ? null + : string.Join(" ", this.Query); + } + } + + /// <summary> + /// Returns a <see cref="PackageFieldMatchOption" /> based on a parameter. + /// </summary> + /// <returns>A <see cref="PackageFieldMatchOption" /> value.</returns> + protected virtual PackageFieldMatchOption GetExactAsMatchOption() + { + return this.Exact + ? PackageFieldMatchOption.Equals + : PackageFieldMatchOption.ContainsCaseInsensitive; + } + + /// <summary> + /// Searches for packages based on the configured parameters. + /// </summary> + /// <param name="behavior">The <see cref="CompositeSearchBehavior" /> value.</param> + /// <param name="limit">The limit on the number of matches returned.</param> + /// <returns>A list of <see cref="MatchResult" /> objects.</returns> + protected IReadOnlyList<MatchResult> FindPackages( + CompositeSearchBehavior behavior, + uint limit) + { + PackageCatalog catalog = this.GetPackageCatalog(behavior); + FindPackagesOptions options = this.GetFindPackagesOptions(limit); + return GetMatchResults(catalog, options); + } + + private static void SetQueryInFindPackagesOptions( + ref FindPackagesOptions options, + PackageFieldMatchOption match, + string value) + { + var selector = ComObjectFactory.Value.CreatePackageMatchFilter(); + selector.Field = PackageMatchField.CatalogDefault; + selector.Value = value ?? string.Empty; + selector.Option = match; + options.Selectors.Add(selector); + } + + private static void AddFilterToFindPackagesOptionsIfNotNull( + ref FindPackagesOptions options, + PackageMatchField field, + PackageFieldMatchOption match, + string value) + { + if (value != null) + { + var filter = ComObjectFactory.Value.CreatePackageMatchFilter(); + filter.Field = field; + filter.Value = value; + filter.Option = match; + options.Filters.Add(filter); + } + } + + private static IReadOnlyList<MatchResult> GetMatchResults( + PackageCatalog catalog, + FindPackagesOptions options) + { + FindPackagesResult result = catalog.FindPackages(options); + if (result.Status == FindPackagesResultStatus.Ok) + { + return result.Matches; + } + else + { + throw new FindPackagesException(result.Status); + } + } + + private PackageCatalog GetPackageCatalog(CompositeSearchBehavior behavior) + { + PackageCatalogReference reference = this.GetPackageCatalogReference(behavior); + ConnectResult result = reference.Connect(); + if (result.Status == ConnectResultStatus.Ok) + { + return result.PackageCatalog; + } + else + { + throw new CatalogConnectException(); + } + } + + private PackageCatalogReference GetPackageCatalogReference(CompositeSearchBehavior behavior) + { + CreateCompositePackageCatalogOptions options = ComObjectFactory.Value.CreateCreateCompositePackageCatalogOptions(); + IReadOnlyList<PackageCatalogReference> references = GetPackageCatalogReferences(this.Source); + for (var i = 0; i < references.Count; i++) + { + options.Catalogs.Add(references[i]); + } + + options.CompositeSearchBehavior = behavior; + return PackageManager.Value.CreateCompositePackageCatalog(options); + } + + private FindPackagesOptions GetFindPackagesOptions(uint limit) + { + var options = ComObjectFactory.Value.CreateFindPackagesOptions(); + SetQueryInFindPackagesOptions(ref options, this.GetExactAsMatchOption(), this.QueryAsJoinedString); + this.AddAttributedFiltersToFindPackagesOptions(ref options, this.GetExactAsMatchOption()); + options.ResultLimit = limit; + return options; + } + + private void AddAttributedFiltersToFindPackagesOptions( + ref FindPackagesOptions options, + PackageFieldMatchOption match) + { + IEnumerable<PropertyInfo> properties = this.GetType() + .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(property => Attribute.IsDefined(property, typeof(FilterAttribute))); + + foreach (PropertyInfo info in properties) + { + if (info.GetCustomAttribute(typeof(FilterAttribute), true) is FilterAttribute attribute) + { + PackageMatchField field = attribute.Field; + string value = info.GetValue(this, null) as string; + AddFilterToFindPackagesOptionsIfNotNull(ref options, field, match, value); + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderExtendedCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderExtendedCommand.cs @@ -0,0 +1,57 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FinderExtendedCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Attributes; + + /// <summary> + /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., + /// the "search" and "list" commands. This class contains an extended set of parameters suited for + /// that purpose. + /// </summary> + public abstract class FinderExtendedCommand : FinderCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="FinderExtendedCommand"/> class. + /// </summary> + /// <param name="psCmdlet">PSCmdlet.</param> + internal FinderExtendedCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Gets or sets the filter that is matched against the tags of the package. + /// </summary> + [Filter(Field = PackageMatchField.Tag)] + protected string Tag { get; set; } + + /// <summary> + /// Gets or sets the filter that is matched against the commands of the package. + /// </summary> + [Filter(Field = PackageMatchField.Command)] + protected string Command { get; set; } + + /// <summary> + /// Gets or sets the maximum number of results returned. + /// </summary> + protected uint Count { get; set; } + + /// <summary> + /// Searches for packages from configured sources. + /// </summary> + /// <param name="behavior">A <see cref="CompositeSearchBehavior" /> value.</param> + /// <returns>A list of <see cref="MatchResult" /> objects.</returns> + protected IReadOnlyList<MatchResult> FindPackages(CompositeSearchBehavior behavior) + { + return this.FindPackages(behavior, this.Count); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs @@ -0,0 +1,155 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InstallCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Helpers; + using Windows.Foundation; + + /// <summary> + /// This is the base class for all commands that parse a <see cref="FindPackagesOptions" /> result + /// from the provided parameters i.e., the "install" and "upgrade" commands. + /// </summary> + public abstract class InstallCommand : PackageCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="InstallCommand"/> class. + /// </summary> + /// <param name="psCmdlet">PSCmdlet.</param> + internal InstallCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Gets or sets the mode to manipulate the package with. + /// </summary> + protected PackageInstallMode Mode { get; set; } = PackageInstallMode.Default; + + /// <summary> + /// Gets or sets the override arguments to be passed on to the installer. + /// </summary> + protected string Override { get; set; } + + /// <summary> + /// Gets or sets the arguments to be passed on to the installer in addition to the defaults. + /// </summary> + protected string Custom { get; set; } + + /// <summary> + /// Gets or sets the installation location. + /// </summary> + protected string Location { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// </summary> + protected bool AllowHashMismatch { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// </summary> + protected bool Force { get; set; } + + /// <summary> + /// Gets or sets the optional HTTP Header to pass on to the REST Source. + /// </summary> + protected string Header { get; set; } + + /// <summary> + /// Gets the install options from the configured parameters. + /// </summary> + /// <param name="version">The <see cref="PackageVersionId" /> to install.</param> + /// <returns>An <see cref="InstallOptions" /> instance.</returns> + protected virtual InstallOptions GetInstallOptions(PackageVersionId version) + { + InstallOptions options = ComObjectFactory.Value.CreateInstallOptions(); + options.AllowHashMismatch = this.AllowHashMismatch; + options.Force = this.Force; + options.PackageInstallMode = this.Mode; + if (version != null) + { + options.PackageVersionId = version; + } + + if (this.Log != null) + { + options.LogOutputPath = this.Log; + } + + if (this.Override != null) + { + options.ReplacementInstallerArguments = this.Override; + } + + // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if they are whitespace + if (!string.IsNullOrWhiteSpace(this.Custom)) + { + options.AdditionalInstallerArguments = this.Custom; + } + + if (this.Location != null) + { + options.PreferredInstallLocation = this.Location; + } + + if (this.Header != null) + { + options.AdditionalPackageCatalogArguments = this.Header; + } + + return options; + } + + /// <summary> + /// Registers callbacks on an asynchronous operation and waits for the results. + /// </summary> + /// <param name="operation">The asynchronous operation.</param> + /// <param name="activity">A <see cref="string" /> instance.</param> + /// <returns>A <see cref="InstallResult" /> instance.</returns> + protected InstallResult RegisterCallbacksAndWait( + IAsyncOperationWithProgress<InstallResult, InstallProgress> operation, + string activity) + { + WriteProgressAdapter adapter = new (this.PsCmdlet); + operation.Progress = (context, progress) => + { + ProgressRecord record = new (1, activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + + if (progress.State == PackageInstallProgressState.Downloading && progress.BytesRequired != 0) + { + record.StatusDescription = $"{progress.BytesDownloaded / 1000000.0f:0.0} MB / {progress.BytesRequired / 1000000.0f:0.0} MB"; + record.PercentComplete = (int)(progress.DownloadProgress * 100); + } + else if (progress.State == PackageInstallProgressState.Installing) + { + record.PercentComplete = (int)(progress.InstallationProgress * 100); + } + + adapter.WriteProgress(record); + }; + operation.Completed = (context, status) => + { + adapter.WriteProgress(new ProgressRecord(1, activity, status.ToString()) + { + RecordType = ProgressRecordType.Completed, + }); + adapter.Completed = true; + }; + System.Console.CancelKeyPress += (sender, e) => + { + operation.Cancel(); + }; + adapter.Wait(); + return operation.GetResults(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs @@ -0,0 +1,114 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PackageCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Extensions; + + /// <summary> + /// This is the base class for commands which operate on a specific package and version i.e., + /// the "install", "uninstall", and "upgrade" commands. + /// </summary> + public abstract class PackageCommand : FinderCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="PackageCommand"/> class. + /// </summary> + /// <param name="psCmdlet">PSCmdlet.</param> + internal PackageCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Gets or sets the package to directly install. + /// </summary> + /// <remarks> + /// Must match the name of the <see cref="CatalogPackage" /> field on the <see cref="MatchResult" /> class. + /// </remarks> + protected CatalogPackage CatalogPackage { get; set; } = null; + + /// <summary> + /// Gets or sets the version to install. + /// </summary> + protected string Version { get; set; } + + /// <summary> + /// Gets or sets the path to the logging file. + /// </summary> + protected string Log { get; set; } + + /// <summary> + /// Executes a command targeting a specific package version. + /// </summary> + /// <param name="behavior">The <see cref="CompositeSearchBehavior" /> value.</param> + /// <param name="callback">The method to call after retrieving the package and version to operate upon.</param> + protected void GetPackageAndExecute( + CompositeSearchBehavior behavior, + Action<CatalogPackage, PackageVersionId> callback) + { + CatalogPackage package = this.GetCatalogPackage(behavior); + PackageVersionId version = this.GetPackageVersionId(package); + if (this.PsCmdlet.ShouldProcess(package.ToString(version))) + { + callback(package, version); + } + } + + private CatalogPackage GetCatalogPackage(CompositeSearchBehavior behavior) + { + if (this.CatalogPackage != null) + { + // The package was already provided via a parameter or the pipeline. + return this.CatalogPackage; + } + else + { + IReadOnlyList<MatchResult> results = this.FindPackages(behavior, 0); + if (results.Count == 1) + { + // Exactly one package matched, so we can just return it. + return results[0].CatalogPackage; + } + else if (results.Count == 0) + { + // No packages matched, we need to throw an error. + throw new NoPackageFoundException(); + } + else + { + // Too many packages matched! The user needs to refine their input. + throw new VagueCriteriaException(results); + } + } + } + + private PackageVersionId GetPackageVersionId(CatalogPackage package) + { + if (this.Version != null) + { + for (var i = 0; i < package.AvailableVersions.Count; i++) + { + if (package.AvailableVersions[i].Version.CompareTo(this.Version) == 0) + { + return package.AvailableVersions[i]; + } + } + + throw new InvalidVersionException(this.Version); + } + else + { + return null; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FinderPackageCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Searches configured sources for packages. + /// </summary> + public sealed class FinderPackageCommand : FinderExtendedCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="FinderPackageCommand"/> class. + /// </summary> + /// <param name="psCmdlet">Caller cmdlet.</param> + /// <param name="id">Package identifier.</param> + /// <param name="name">Name of package.</param> + /// <param name="moniker">Moniker of package.</param> + /// <param name="source">Source to search. If null, all are searched.</param> + /// <param name="query">Match against any field of a package.</param> + /// <param name="exact">Do exact match.</param> + /// <param name="tag">Tag of the package.</param> + /// <param name="command">Command of the package.</param> + /// <param name="count">Max results to return.</param> + public FinderPackageCommand( + PSCmdlet psCmdlet, + string id, + string name, + string moniker, + string source, + string[] query, + bool exact, + string tag, + string command, + uint count) + : base(psCmdlet) + { + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + this.Exact = exact; + + // FinderExtendedCommand + this.Tag = tag; + this.Command = command; + this.Count = count; + } + + /// <summary> + /// Process find package command. + /// </summary> + public void Find() + { + var results = this.FindPackages(CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs); + for (var i = 0; i < results.Count; i++) + { + this.PsCmdlet.WriteObject(new PSFoundCatalogPackage(results[i].CatalogPackage)); + } + } + + /// <summary> + /// Process get package command. + /// </summary> + public void Get() + { + var results = this.FindPackages(CompositeSearchBehavior.LocalCatalogs); + for (var i = 0; i < results.Count; i++) + { + this.PsCmdlet.WriteObject(new PSInstalledCatalogPackage(results[i].CatalogPackage)); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs @@ -0,0 +1,153 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InstallerPackageCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Extensions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.Properties; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Windows.System; + + /// <summary> + /// Installs or updates a package from the pipeline or from a configured source. + /// </summary> + public sealed class InstallerPackageCommand : InstallCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="InstallerPackageCommand"/> class. + /// </summary> + /// <param name="psCmdlet">Caller cmdlet.</param> + /// <param name="psInstallMode">Install mode to use.</param> + /// <param name="override">Override arguments to be passed on to the installer.</param> + /// <param name="custom">Additional arguments.</param> + /// <param name="location">Installation location.</param> + /// <param name="allowHashMisMatch">To skip the installer hash validation check.</param> + /// <param name="force">To continue upon non security related failures.</param> + /// <param name="header">HTTP Header to pass on to the REST Source.</param> + /// <param name="psCatalogPackage">PSCatalogPackage.</param> + /// <param name="version">Version to install.</param> + /// <param name="log">Logging file location.</param> + /// <param name="id">Package identifier.</param> + /// <param name="name">Name of package.</param> + /// <param name="moniker">Moniker of package.</param> + /// <param name="source">Source to search. If null, all are searched.</param> + /// <param name="query">Match against any field of a package.</param> + /// <param name="exact">Do exact match.</param> + public InstallerPackageCommand( + PSCmdlet psCmdlet, + string psInstallMode, + string @override, + string custom, + string location, + bool allowHashMisMatch, + bool force, + string header, + PSCatalogPackage psCatalogPackage, + string version, + string log, + string id, + string name, + string moniker, + string source, + string[] query, + bool exact) + : base(psCmdlet) + { + // InstallCommand. + this.Mode = PSEnumHelpers.ToPackageInstallMode(psInstallMode); + this.Override = @override; + this.Custom = custom; + this.Location = location; + this.AllowHashMismatch = allowHashMisMatch; + this.Force = force; + this.Header = header; + + // PackageCommand. + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage.CatalogPackageCOM; + } + + this.Version = version; + this.Log = log; + + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + this.Exact = exact; + } + + /// <summary> + /// Process install package command. + /// </summary> + /// <param name="psPackageInstallScope">PSPackageInstallScope.</param> + /// <param name="psProcessorArchitecture">PSProcessorArchitecture.</param> + public void Install( + string psPackageInstallScope, + string psProcessorArchitecture) + { + this.GetPackageAndExecute(CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, (package, version) => + { + InstallOptions options = this.GetInstallOptions(version); + if (psProcessorArchitecture != "Default") + { + options.AllowedArchitectures.Clear(); + options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); + } + + options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + + InstallResult result = this.InstallPackage(package, options); + this.PsCmdlet.WriteObject(new PSInstallResult(result)); + }); + } + + /// <summary> + /// Process update package command. + /// </summary> + /// <param name="includeUnknown">If updating to an unknown version is allowed.</param> + public void Update(bool includeUnknown) + { + this.GetPackageAndExecute(CompositeSearchBehavior.LocalCatalogs, (package, version) => + { + InstallOptions options = this.GetInstallOptions(version); + options.AllowUpgradeToUnknownVersion = includeUnknown; + + InstallResult result = this.UpgradePackage(package, options); + this.PsCmdlet.WriteObject(new PSInstallResult(result)); + }); + } + + private InstallResult InstallPackage( + CatalogPackage package, + InstallOptions options) + { + var operation = PackageManager.Value.InstallPackageAsync(package, options); + return this.RegisterCallbacksAndWait(operation, string.Format( + Resources.ProgressRecordActivityInstalling, + package.Name)); + } + + private InstallResult UpgradePackage( + CatalogPackage package, + InstallOptions options) + { + var operation = PackageManager.Value.UpgradePackageAsync(package, options); + return this.RegisterCallbacksAndWait( + operation, + string.Format( + Resources.ProgressRecordActivityUpdating, + package.Name)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/SourceCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/SourceCommand.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// <copyright file="SourceCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Wrapper for source cmdlets. + /// </summary> + public sealed class SourceCommand : ClientCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="SourceCommand"/> class. + /// Wrapper for Source commands. + /// </summary> + /// <param name="psCmdlet">Caller cmdlet.</param> + public SourceCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Get-WinGetSource. + /// </summary> + /// <param name="name">Optional name.</param> + public void Get(string name) + { + var results = GetPackageCatalogReferences(name); + for (var i = 0; i < results.Count; i++) + { + this.PsCmdlet.WriteObject(new PSSourceResult(results[i])); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs @@ -0,0 +1,139 @@ +// ----------------------------------------------------------------------------- +// <copyright file="UninstallPackageCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Extensions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.Properties; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// <summary> + /// Uninstalls a package from the local system. + /// </summary> + public sealed class UninstallPackageCommand : PackageCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="UninstallPackageCommand"/> class. + /// </summary> + /// <param name="psCmdlet">Caller cmdlet.</param> + /// <param name="psCatalogPackage">PSCatalogPackage.</param> + /// <param name="version">Version to install.</param> + /// <param name="log">Logging file location.</param> + /// <param name="id">Package identifier.</param> + /// <param name="name">Name of package.</param> + /// <param name="moniker">Moniker of package.</param> + /// <param name="source">Source to search. If null, all are searched.</param> + /// <param name="query">Match against any field of a package.</param> + /// <param name="exact">Do exact match.</param> + public UninstallPackageCommand( + PSCmdlet psCmdlet, + PSCatalogPackage psCatalogPackage, + string version, + string log, + string id, + string name, + string moniker, + string source, + string[] query, + bool exact) + : base(psCmdlet) + { + // PackageCommand. + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage.CatalogPackageCOM; + } + + this.Version = version; + this.Log = log; + + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + this.Exact = exact; + } + + /// <summary> + /// Process uninstall package. + /// </summary> + /// <param name="psPackageUninstallMode">PSPackageUninstallMode.</param> + /// <param name="force">Force.</param> + public void Uninstall( + string psPackageUninstallMode, + bool force) + { + this.GetPackageAndExecute(CompositeSearchBehavior.LocalCatalogs, (package, version) => + { + UninstallOptions options = this.GetUninstallOptions(version, PSEnumHelpers.ToPackageUninstallMode(psPackageUninstallMode), force); + UninstallResult result = this.UninstallPackage(package, options); + this.PsCmdlet.WriteObject(new PSUninstallResult(result)); + }); + } + + private UninstallOptions GetUninstallOptions( + PackageVersionId version, + PackageUninstallMode packageUninstallMode, + bool force) + { + var options = ComObjectFactory.Value.CreateUninstallOptions(); + options.Force = force; + if (this.Log != null) + { + options.LogOutputPath = this.Log; + } + + options.PackageUninstallMode = packageUninstallMode; + + if (version != null) + { + options.PackageVersionId = version; + } + + return options; + } + + private UninstallResult UninstallPackage( + CatalogPackage package, + UninstallOptions options) + { + string activity = string.Format( + Resources.ProgressRecordActivityUninstalling, + package.Name); + + var operation = PackageManager.Value.UninstallPackageAsync(package, options); + WriteProgressAdapter adapter = new (this.PsCmdlet); + operation.Progress = (context, progress) => + { + adapter.WriteProgress(new ProgressRecord(1, activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }); + }; + operation.Completed = (context, status) => + { + adapter.WriteProgress(new ProgressRecord(1, activity, status.ToString()) + { + RecordType = ProgressRecordType.Completed, + }); + adapter.Completed = true; + }; + Console.CancelKeyPress += (sender, e) => + { + operation.Cancel(); + }; + adapter.Wait(); + return operation.GetResults(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs @@ -0,0 +1,361 @@ +// ----------------------------------------------------------------------------- +// <copyright file="UserSettingsCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// <summary> + /// Class used by the user settings cmdlets. + /// </summary> + public sealed class UserSettingsCommand : BaseCommand + { + private const string SchemaKey = "$schema"; + private const string SchemaValue = "https://aka.ms/winget-settings.schema.json"; + + private static readonly string WinGetSettingsFilePath; + + static UserSettingsCommand() + { + var wingetCliWrapper = new WingetCLIWrapper(); + var settingsResult = wingetCliWrapper.RunCommand("settings", "export"); + + // Read the user settings file property. + var serialized = JObject.Parse(settingsResult.StdOut); + WinGetSettingsFilePath = (string)serialized.GetValue("userSettingsFile"); + } + + /// <summary> + /// Initializes a new instance of the <see cref="UserSettingsCommand"/> class. + /// </summary> + /// <param name="psCmdlet">PSCmdlet.</param> + public UserSettingsCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Get-WinGetUserSettings. + /// </summary> + public void Get() + { + this.PsCmdlet.WriteObject(this.GetLocalSettingsAsHashtable()); + } + + /// <summary> + /// Test-WinGetUserSettings. + /// </summary> + /// <param name="userSettings">Input user settings.</param> + /// <param name="ignoreNotSet">Ignore comparing settings that are not part of the input.</param> + public void Test(Hashtable userSettings, bool ignoreNotSet) + { + this.PsCmdlet.WriteObject(this.CompareUserSettings(userSettings, ignoreNotSet)); + } + + /// <summary> + /// Set-WinGetUserSettings. + /// </summary> + /// <param name="userSettings">Input user settings.</param> + /// <param name="merge">Merge the current user settings and the input settings.</param> + public void Set(Hashtable userSettings, bool merge) + { + var newSettings = HashtableToJObject(userSettings); + + // Merge settings. + if (merge) + { + var currentSettings = this.LocalSettingsFileToJObject(); + + // To make the input settings triumph, they need to be merged into the existing settings. + currentSettings.Merge(newSettings, new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Union, + MergeNullValueHandling = MergeNullValueHandling.Ignore, + }); + + newSettings = currentSettings; + } + + // Add schema if not there. + if (!newSettings.ContainsKey(SchemaKey)) + { + newSettings.Add(SchemaKey, SchemaValue); + } + + var orderedSettings = this.CreateAlphabeticallyOrderedJObject(newSettings); + + // Write settings. + var settingsJson = orderedSettings.ToString(Formatting.Indented); + File.WriteAllText( + WinGetSettingsFilePath, + settingsJson); + + this.PsCmdlet.WriteObject(this.ConvertToHashtable(settingsJson)); + } + + private static JObject HashtableToJObject(Hashtable hashtable) + { + return (JObject)JToken.FromObject(hashtable); + } + + private Hashtable GetLocalSettingsAsHashtable() + { + var content = File.Exists(WinGetSettingsFilePath) ? + File.ReadAllText(WinGetSettingsFilePath) : + string.Empty; + + return this.ConvertToHashtable(content); + } + + private JObject LocalSettingsFileToJObject() + { + try + { + return File.Exists(WinGetSettingsFilePath) ? + JObject.Parse(File.ReadAllText(WinGetSettingsFilePath)) : + new JObject(); + } + catch (JsonReaderException e) + { + this.PsCmdlet.WriteDebug(e.Message); + throw new UserSettingsReadException(e); + } + } + + private Hashtable ConvertToHashtable(string content) + { + if (string.IsNullOrEmpty(content)) + { + return new Hashtable(); + } + + // This is based of https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs. + // So we can convert JSON to Hashtable for Windows PowerShell and PowerShell Core. + try + { + var obj = JsonConvert.DeserializeObject( + content, + new JsonSerializerSettings + { + // This TypeNameHandling setting is required to be secure. + TypeNameHandling = TypeNameHandling.None, + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + MaxDepth = 1024, + }); + + // It only makes sense that the deserialized object is a dictionary to start. + return obj switch + { + JObject dictionary => this.PopulateHashTableFromJDictionary(dictionary), + _ => throw new UserSettingsReadException() + }; + } + catch (Exception e) + { + throw new UserSettingsReadException(e); + } + } + + private Hashtable PopulateHashTableFromJDictionary(JObject entries) + { + Hashtable result = new (entries.Count); + foreach (var entry in entries) + { + switch (entry.Value) + { + case JArray list: + { + // Array + var listResult = this.PopulateHashTableFromJArray(list); + result.Add(entry.Key, listResult); + break; + } + + case JObject dic: + { + // Dictionary + var dicResult = this.PopulateHashTableFromJDictionary(dic); + result.Add(entry.Key, dicResult); + break; + } + + case JValue value: + { + result.Add(entry.Key, value.Value); + break; + } + } + } + + return result; + } + + private ICollection<object> PopulateHashTableFromJArray(JArray list) + { + var result = new object[list.Count]; + + for (var index = 0; index < list.Count; index++) + { + var element = list[index]; + + switch (element) + { + case JArray array: + { + // Array + var listResult = this.PopulateHashTableFromJArray(array); + result[index] = listResult; + break; + } + + case JObject dic: + { + // Dictionary + var dicResult = this.PopulateHashTableFromJDictionary(dic); + result[index] = dicResult; + break; + } + + case JValue value: + { + result[index] = value.Value; + break; + } + } + } + + return result; + } + + private bool CompareUserSettings(Hashtable userSettings, bool ignoreNotSet) + { + try + { + var currentSettings = this.LocalSettingsFileToJObject(); + var newSettings = HashtableToJObject(userSettings); + + // Don't fail because of the schema. + if (currentSettings.ContainsKey(SchemaKey)) + { + currentSettings.Remove(SchemaKey); + } + + if (newSettings.ContainsKey(SchemaKey)) + { + newSettings.Remove(SchemaKey); + } + + if (ignoreNotSet) + { + return this.PartialDeepEquals(newSettings, currentSettings); + } + + return JToken.DeepEquals(newSettings, currentSettings); + } + catch (Exception e) + { + this.PsCmdlet.WriteDebug(e.Message); + return false; + } + } + + /// <summary> + /// Partially compares json. All properties and values of json must exist and have the same value + /// as otherJson. + /// This doesn't support deep JArray object comparison, but we don't have arrays of type object so far :). + /// </summary> + /// <param name="json">Main json.</param> + /// <param name="otherJson">otherJson.</param> + /// <returns>True is otherJson partially contains json.</returns> + private bool PartialDeepEquals(JToken json, JToken otherJson) + { + if (JToken.DeepEquals(json, otherJson)) + { + return true; + } + + // If they are a JValue (string, integer, date, etc) or they are a JArray and DeepEquals fails then not equal. + if ((json is JValue && otherJson is JValue) || + (json is JArray && otherJson is JArray)) + { + this.PsCmdlet.WriteDebug($"'{json.ToString(Newtonsoft.Json.Formatting.None)}' != " + + $"'{otherJson.ToString(Newtonsoft.Json.Formatting.None)}'"); + return false; + } + + // If its not the same type then don't bother. + if (json.Type != otherJson.Type) + { + this.PsCmdlet.WriteDebug($"Mismatch types '{json.ToString(Newtonsoft.Json.Formatting.None)}' " + + $"'{otherJson.ToString(Newtonsoft.Json.Formatting.None)}'"); + return false; + } + + // Look deeply. + if (json.Type == JTokenType.Object) + { + var jObject = (JObject)json; + var otherJObject = (JObject)otherJson; + + var properties = jObject.Properties(); + foreach (var property in properties) + { + // If the property is not there then give up. + if (!otherJObject.ContainsKey(property.Name)) + { + this.PsCmdlet.WriteDebug($"{property.Name} not found."); + return false; + } + + if (!this.PartialDeepEquals(property.Value, otherJObject.GetValue(property.Name))) + { + // Found inequality within a property. We are done. + return false; + } + } + } + + return true; + } + + /// <summary> + /// Helper method to order alphabetically properties. Newtonsoft doesn't have a nice way + /// to do it via a custom JsonConverter. + /// </summary> + /// <param name="jObject">JObject.</param> + /// <returns>New ordered JObject.</returns> + private JObject CreateAlphabeticallyOrderedJObject(JObject jObject) + { + JObject newJObject = new (); + var orderedProperties = jObject.Properties().OrderBy(p => p.Name, StringComparer.Ordinal); + foreach (var property in orderedProperties) + { + if (property.Value.Type == JTokenType.Object) + { + newJObject.Add( + property.Name, + this.CreateAlphabeticallyOrderedJObject((JObject)property.Value)); + } + else + { + newJObject.Add(property); + } + } + + return newJObject; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs @@ -0,0 +1,35 @@ +// ----------------------------------------------------------------------------- +// <copyright file="VersionCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + + /// <summary> + /// Version commands. + /// </summary> + public sealed class VersionCommand : BaseCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="VersionCommand"/> class. + /// </summary> + /// <param name="psCmdlet">The caller cmdlet.</param> + public VersionCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Get-WinGetVersion. Gets the currently installed winget version. + /// </summary> + public void Get() + { + this.PsCmdlet.WriteObject(WinGetVersion.InstalledWinGetVersion.TagVersion); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -0,0 +1,211 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetPackageManagerCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. + /// </summary> + public sealed class WinGetPackageManagerCommand : 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" }; + + /// <summary> + /// Initializes a new instance of the <see cref="WinGetPackageManagerCommand"/> class. + /// </summary> + /// <param name="psCmdlet">Cmdlet being executed.</param> + public WinGetPackageManagerCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// <summary> + /// Asserts winget version is the latest version on winget-cli. + /// </summary> + /// <param name="preRelease">Use prerelease version on GitHub.</param> + public void AssertUsingLatest(bool preRelease) + { + var gitHubRelease = new GitHubRelease(); + string expectedVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + this.Assert(expectedVersion); + } + + /// <summary> + /// Asserts the version installed is the specified. + /// </summary> + /// <param name="expectedVersion">The expected version.</param> + public void Assert(string expectedVersion) + { + WinGetIntegrity.AssertWinGet(this.PsCmdlet, expectedVersion); + } + + /// <summary> + /// Repairs winget using the latest version on winget-cli. + /// </summary> + /// <param name="preRelease">Use prerelease version on GitHub.</param> + public void RepairUsingLatest(bool preRelease) + { + var gitHubRelease = new GitHubRelease(); + string expectedVersion = gitHubRelease.GetLatestVersionTagName(preRelease); + this.Repair(expectedVersion); + } + + /// <summary> + /// Repairs winget if needed. + /// </summary> + /// <param name="expectedVersion">The expected version, if any.</param> + public void Repair(string expectedVersion) + { + int result = Failed; + + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, expectedVersion); + this.PsCmdlet.WriteDebug($"Integrity category type: {integrityCategory}"); + + if (integrityCategory == IntegrityCategory.Installed || + integrityCategory == IntegrityCategory.UnexpectedVersion) + { + result = this.VerifyWinGetInstall(integrityCategory, expectedVersion); + } + else if (integrityCategory == IntegrityCategory.NotInPath) + { + this.RepairEnvPath(); + + // Now try again and get the desired winget version if needed. + var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, expectedVersion); + this.PsCmdlet.WriteDebug($"Integrity category after fixing PATH {newIntegrityCategory}"); + result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); + } + else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) + { + var appxModule = new AppxModuleHelper(this.PsCmdlet); + appxModule.RegisterAppInstaller(); + + // Now try again and get the desired winget version if needed. + var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, expectedVersion); + this.PsCmdlet.WriteDebug($"Integrity category after registering {newIntegrityCategory}"); + result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); + } + else if (integrityCategory == IntegrityCategory.AppInstallerNotInstalled || + integrityCategory == IntegrityCategory.AppInstallerNotSupported || + integrityCategory == IntegrityCategory.Failure) + { + // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGetPackageManager. + // When there is not version specified, we don't want to assume an empty version means latest, but in + // this particular case we need to. + if (string.IsNullOrEmpty(expectedVersion)) + { + var gitHubRelease = new GitHubRelease(); + expectedVersion = gitHubRelease.GetLatestVersionTagName(false); + } + + if (this.DownloadAndInstall(expectedVersion, false)) + { + result = Succeeded; + } + else + { + this.PsCmdlet.WriteDebug($"Failed installing {expectedVersion}"); + } + } + else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) + { + // Sorry, but the user has to manually enabled it. + this.PsCmdlet.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); + } + else + { + this.PsCmdlet.WriteInformation(Resources.WinGetNotSupportedMessage, WriteInformationTags); + } + + this.PsCmdlet.WriteObject(result); + } + + private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expectedVersion) + { + if (integrityCategory == IntegrityCategory.Installed) + { + // Nothing to do + this.PsCmdlet.WriteDebug($"WinGet is in a good state."); + return Succeeded; + } + else if (integrityCategory == IntegrityCategory.UnexpectedVersion) + { + // The versions are different, download and install. + if (!this.InstallDifferentVersion(new WinGetVersion(expectedVersion))) + { + this.PsCmdlet.WriteDebug($"Failed installing {expectedVersion}"); + } + else + { + return Succeeded; + } + } + + return Failed; + } + + private bool InstallDifferentVersion(WinGetVersion toInstallVersion) + { + var installedVersion = WinGetVersion.InstalledWinGetVersion; + + this.PsCmdlet.WriteDebug($"Installed WinGet version {installedVersion.TagVersion}"); + this.PsCmdlet.WriteDebug($"Installing WinGet version {toInstallVersion.TagVersion}"); + + bool downgrade = false; + if (installedVersion.CompareAsDeployment(toInstallVersion) > 0) + { + downgrade = true; + } + + return this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); + } + + private bool DownloadAndInstall(string versionTag, bool downgrade) + { + // Download and install. + var gitHubRelease = new GitHubRelease(); + var downloadedMsixBundlePath = gitHubRelease.DownloadRelease(versionTag); + + var appxModule = new AppxModuleHelper(this.PsCmdlet); + appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); + + // Verify that is installed + var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this.PsCmdlet, versionTag); + if (integrityCategory != IntegrityCategory.Installed) + { + return false; + } + + this.PsCmdlet.WriteDebug($"Installed WinGet version {versionTag}"); + return true; + } + + private void RepairEnvPath() + { + // Add windows app path to user PATH environment variable + Utilities.AddWindowsAppToPath(); + + // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. + string envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); + string envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); + string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; + this.PsCmdlet.SessionState.PSVariable.Set(EnvPath, newPwshPathEnv); + + this.PsCmdlet.WriteDebug($"PATH environment variable updated"); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------------- +// <copyright file="Constants.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + /// <summary> + /// This class contains all of the configurable constants for this project. + /// </summary> + internal static class Constants + { + /// <summary> + /// WinGet package family name. + /// </summary> +#if USE_PROD_CLSIDS + public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; +#else + public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; +#endif + + /// <summary> + /// Winget executable name. + /// </summary> +#if USE_PROD_CLSIDS + public const string WinGetExe = "winget.exe"; +#else + public const string WinGetExe = "wingetdev.exe"; +#endif + + /// <summary> + /// Name of PATH environment variable. + /// </summary> + public const string PathEnvVar = "PATH"; + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ErrorCode.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + /// <summary> + /// Error code constants. + /// </summary> + public static class ErrorCode + { + /// <summary> + /// Error code for ERROR_FILE_NOT_FOUND. + /// </summary> + public const int FileNotFound = unchecked((int)0x80070002); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------------- +// <copyright file="IntegrityCategory.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + /// <summary> + /// The type of the integrity check failure. + /// </summary> + public enum IntegrityCategory + { + /// <summary> + /// WinGet is correctly installed. + /// </summary> + Installed, + + /// <summary> + /// The version installed is not what is expected. + /// </summary> + UnexpectedVersion, + + /// <summary> + /// Unknown reason. + /// </summary> + Unknown, + + /// <summary> + /// A failure resulted on a simple winget command that shouldn't happen. + /// </summary> + Failure, + + /// <summary> + /// WindowsAppPath not in PATH environment variable. + /// </summary> + NotInPath, + + /// <summary> + /// Winget's app execution alias disabled. + /// </summary> + AppExecutionAliasDisabled, + + /// <summary> + /// Windows OS is not supported. + /// </summary> + OsNotSupported, + + /// <summary> + /// AppInstaller package is not installed. + /// </summary> + AppInstallerNotInstalled, + + /// <summary> + /// AppInstaller package is not registered. + /// </summary> + AppInstallerNotRegistered, + + /// <summary> + /// Installed App Installer package is not supported. + /// </summary> + AppInstallerNotSupported, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Utilities.cs @@ -0,0 +1,105 @@ +// ----------------------------------------------------------------------------- +// <copyright file="Utilities.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + using System; + using System.Security.Principal; + using System.Threading; + + /// <summary> + /// This class contains various helper methods for this project. + /// </summary> + internal static class Utilities + { + /// <summary> + /// Gets a value indicating whether the current assembly is executing in an administrative context. + /// </summary> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ExecutingAsAdministrator + { + get + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new (identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + /// <summary> + /// Gets a value indicating whether the current assembly is executing as a SYSTEM user. + /// </summary> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ExecutingAsSystem + { + get + { + return WindowsIdentity.GetCurrent().IsSystem; + } + } + + /// <summary> + /// Gets a value indicating whether the current execution context will use in-proc winget. + /// </summary> + public static bool UsesInProcWinget + { + get + { + return ExecutingAsSystem; + } + } + + /// <summary> + /// Gets a value indicating whether the current thread is executing as STA. + /// </summary> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ThreadIsSTA + { + get + { + return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; + } + } + + /// <summary> + /// Gets the windows app path for local app data. + /// </summary> + public static string LocalDataWindowsAppPath + { + get + { + return Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\WindowsApps"); + } + } + + /// <summary> + /// Gets the windows app path for program files. + /// </summary> + public static string ProgramFilesWindowsAppPath + { + get + { + return Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WindowsApps"); + } + } + + /// <summary> + /// Adds the WindowsApp local app data path to the user environment path. + /// </summary> + 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.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -0,0 +1,165 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetIntegrity.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + using System; + using System.ComponentModel; + using System.IO; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Validates winget runs correctly. + /// </summary> + internal static class WinGetIntegrity + { + /// <summary> + /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. + /// </summary> + /// <param name="psCmdlet">The calling cmdlet.</param> + /// <param name="expectedVersion">Expected version.</param> + public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) + { + // In-proc shouldn't have other dependencies and thus should be ok. + if (Utilities.UsesInProcWinget) + { + // Only check the OS version support for in-proc + if (!IsSupportedOSVersion()) + { + throw new WinGetIntegrityException(IntegrityCategory.OsNotSupported); + } + + return; + } + + 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(psCmdlet)); + } + 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); + } + + // WinGet is installed. Verify version if needed. + if (!string.IsNullOrEmpty(expectedVersion)) + { + // 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, + string.Format( + Resources.IntegrityUnexpectedVersionMessage, + installedVersion, + expectedVersion)); + } + } + } + + /// <summary> + /// Verifies winget runs correctly. + /// </summary> + /// <param name="psCmdlet">The calling cmdlet.</param> + /// <param name="expectedVersion">Expected version.</param> + /// <returns>Integrity category.</returns> + public static IntegrityCategory GetIntegrityCategory(PSCmdlet psCmdlet, string expectedVersion) + { + try + { + AssertWinGet(psCmdlet, expectedVersion); + } + catch (WinGetIntegrityException e) + { + return e.Category; + } + + return IntegrityCategory.Installed; + } + + private static IntegrityCategory GetReason(PSCmdlet psCmdlet) + { + // 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... 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)) + { + // 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\ + + // Check OS version + if (!IsSupportedOSVersion()) + { + 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(psCmdlet); + 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; + } + + private static bool IsSupportedOSVersion() + { + // 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; + return osVersion.CompareTo(minWindowsVersion) >= 0; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/CatalogConnectException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/CatalogConnectException.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------------- +// <copyright file="CatalogConnectException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Failed connecting to catalog. + /// </summary> + [Serializable] + public class CatalogConnectException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="CatalogConnectException"/> class. + /// </summary> + public CatalogConnectException() + : base(Resources.CatalogConnectExceptionMessage) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/FindPackagesException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/FindPackagesException.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FindPackagesException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Raised when there is an error searching for packages. + /// </summary> + [Serializable] + public class FindPackagesException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="FindPackagesException"/> class. + /// </summary> + /// <param name="status">A <see cref="FindPackagesResultStatus" /> value.</param> + public FindPackagesException(FindPackagesResultStatus status) + : base(string.Format( + Resources.FindPackagesExceptionMessage, + status.ToString())) + { + this.Status = status; + } + + /// <summary> + /// Gets the error status. + /// </summary> + public FindPackagesResultStatus Status { get; private set; } + } +}+ \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidSourceException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidSourceException.cs @@ -0,0 +1,33 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InvalidSourceException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Invalid source. + /// </summary> + [Serializable] + public class InvalidSourceException : ArgumentException + { + /// <summary> + /// Initializes a new instance of the <see cref="InvalidSourceException"/> class. + /// </summary> + /// <param name="sourceName">Source name.</param> + public InvalidSourceException(string sourceName) + : base(string.Format(Resources.InvalidSourceExceptionMessage, sourceName)) + { + this.SourceName = sourceName; + } + + /// <summary> + /// Gets the source name. + /// </summary> + public string SourceName { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidVersionException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidVersionException.cs @@ -0,0 +1,33 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InvalidVersionException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Invalid version. + /// </summary> + [Serializable] + public class InvalidVersionException : ArgumentException + { + /// <summary> + /// Initializes a new instance of the <see cref="InvalidVersionException"/> class. + /// </summary> + /// <param name="version">Version.</param> + public InvalidVersionException(string version) + : base(string.Format(Resources.InvalidVersionExceptionMessage, version)) + { + this.Version = version; + } + + /// <summary> + /// Gets the version. + /// </summary> + public string Version { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/NoPackageFoundException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/NoPackageFoundException.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------------- +// <copyright file="NoPackageFoundException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// No package found. + /// </summary> + [Serializable] + public class NoPackageFoundException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="NoPackageFoundException"/> class. + /// </summary> + public NoPackageFoundException() + : base(Resources.NoPackageFoundExceptionMessage) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/SingleThreadedApartmentException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/SingleThreadedApartmentException.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------------- +// <copyright file="SingleThreadedApartmentException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// No package found. + /// </summary> + [Serializable] + public class SingleThreadedApartmentException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="SingleThreadedApartmentException"/> class. + /// </summary> + public SingleThreadedApartmentException() + : base(Resources.SingleThreadedApartmentNotSupportedMessage) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/UserSettingsReadException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/UserSettingsReadException.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------------- +// <copyright file="UserSettingsReadException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Settings.json file is invalid. + /// </summary> + [Serializable] + public class UserSettingsReadException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="UserSettingsReadException"/> class. + /// </summary> + public UserSettingsReadException() + : base(Resources.UserSettingsReadException) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UserSettingsReadException"/> class. + /// </summary> + /// <param name="inner">Inner exception.</param> + public UserSettingsReadException(Exception inner) + : base(Resources.UserSettingsReadException, inner) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/VagueCriteriaException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/VagueCriteriaException.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// <copyright file="VagueCriteriaException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Extensions; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Raised when search criteria for installing or updating a package is too vague. + /// </summary> + [Serializable] + public class VagueCriteriaException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="VagueCriteriaException"/> class. + /// </summary> + /// <param name="results">The list of conflicting packages of length at least two.</param> + public VagueCriteriaException(IReadOnlyList<MatchResult> results) + : base(string.Format( + Resources.VagueCriteriaExceptionMessage, + results[0].CatalogPackage.ToString(null), + results[1].CatalogPackage.ToString(null), + results.Count - 2)) + { + this.MatchResults = results; + } + + /// <summary> + /// Gets the list of conflicting packages. + /// </summary> + public IReadOnlyList<MatchResult> MatchResults { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLIException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLIException.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetCLIException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// WinGet cli exception. + /// </summary> + public class WinGetCLIException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="WinGetCLIException"/> class. + /// </summary> + /// <param name="command">Command.</param> + /// <param name="parameters">Parameters.</param> + /// <param name="exitCode">Exit code.</param> + /// <param name="stdOut">Standard output.</param> + /// <param name="stdErr">Standard error.</param> + public WinGetCLIException(string command, string parameters, int exitCode, string stdOut, string stdErr) + : base(string.Format(Resources.WinGetCLIExceptionMessage, command, exitCode)) + { + this.Command = command; + this.Parameters = parameters; + this.ExitCode = exitCode; + this.StdOut = stdOut; + this.StdErr = stdErr; + } + + /// <summary> + /// Gets the command. + /// </summary> + public string Command { get; private set; } + + /// <summary> + /// Gets the parameters. + /// </summary> + public string Parameters { get; private set; } + + /// <summary> + /// Gets the exit code. + /// </summary> + public int ExitCode { get; private set; } + + /// <summary> + /// Gets the standard output. + /// </summary> + public string StdOut { get; private set; } + + /// <summary> + /// Gets the standard error. + /// </summary> + public string StdErr { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLITimeoutException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLITimeoutException.cs @@ -0,0 +1,27 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetCLITimeoutException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Time out exception for a winget cli command. + /// </summary> + public class WinGetCLITimeoutException : TimeoutException + { + /// <summary> + /// Initializes a new instance of the <see cref="WinGetCLITimeoutException"/> class. + /// </summary> + /// <param name="command">Command.</param> + /// <param name="parameters">Parameters.</param> + public WinGetCLITimeoutException(string command, string parameters) + : base(string.Format(Resources.WinGetCLITimeoutExceptionMessage, command, parameters)) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetIntegrityException.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// WinGet Integrity exception. + /// </summary> + public class WinGetIntegrityException : RuntimeException + { + /// <summary> + /// Initializes a new instance of the <see cref="WinGetIntegrityException"/> class. + /// </summary> + /// <param name="category">Category failure.</param> + public WinGetIntegrityException(IntegrityCategory category) + : base(GetMessage(category)) + { + this.Category = category; + } + + /// <summary> + /// Initializes a new instance of the <see cref="WinGetIntegrityException"/> class. + /// </summary> + /// <param name="category">Category failure.</param> + /// <param name="inner">Inner exception.</param> + public WinGetIntegrityException(IntegrityCategory category, Exception inner) + : base(GetMessage(category), inner) + { + this.Category = category; + } + + /// <summary> + /// Initializes a new instance of the <see cref="WinGetIntegrityException"/> class. + /// </summary> + /// <param name="category">Category failure.</param> + /// <param name="message">Message.</param> + public WinGetIntegrityException(IntegrityCategory category, string message) + : base(message) + { + this.Category = category; + } + + /// <summary> + /// Gets the category of the integrity failure. + /// </summary> + 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.Engine/Extensions/CatalogPackageExtensions.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/CatalogPackageExtensions.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------------- +// <copyright file="CatalogPackageExtensions.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Extensions +{ + using Microsoft.Management.Deployment; + + /// <summary> + /// Extensions for the <see cref="CatalogPackage" /> class. + /// </summary> + internal static class CatalogPackageExtensions + { + /// <summary> + /// Converts a <see cref="CatalogPackage" /> to a string previewing the specified version. + /// </summary> + /// <param name="package">A <see cref="CatalogPackage" /> instance.</param> + /// <param name="version">A <see cref="PackageVersionId" /> instance. If null, the latest available version is used.</param> + /// <returns>A <see cref="string" /> instance.</returns> + public static string ToString( + this CatalogPackage package, + PackageVersionId version) + { + if ((version != null) || (package.AvailableVersions.Count > 0)) + { + string versionString = (version is null) + ? package.AvailableVersions[0].Version + : version.Version; + return $"{package.Name} [{package.Id}] Version {versionString}"; + } + else + { + // There were no available versions! + return $"{package.Name} [{package.Id}]"; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -0,0 +1,214 @@ +// ----------------------------------------------------------------------------- +// <copyright file="AppxModuleHelper.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Resources; + using System.Runtime.InteropServices; + using System.Text; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Properties; + + /// <summary> + /// Helper to make calls to the Appx module. + /// </summary> + 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 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"; + private const string PackageFullName = "PackageFullName"; + + // 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; + + /// <summary> + /// Initializes a new instance of the <see cref="AppxModuleHelper"/> class. + /// </summary> + /// <param name="psCmdlet">The calling cmdlet.</param> + public AppxModuleHelper(PSCmdlet psCmdlet) + { + 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.psCmdlet.InvokeCommand.InvokeScript(GetAppxModule); + if (appxModule is null) + { + this.psCmdlet.InvokeCommand.InvokeScript(ImportModuleCore); + } +#endif + } + + /// <summary> + /// Calls Get-AppxPackage Microsoft.DesktopAppInstaller. + /// </summary> + /// <returns>Result of Get-AppxPackage.</returns> + public PSObject GetAppInstallerObject() + { + return this.GetAppxObject(AppInstallerName); + } + + /// <summary> + /// Gets the string value a property from the Get-AppxPackage object of AppInstaller. + /// </summary> + /// <param name="propertyName">Property name.</param> + /// <returns>Value, null if doesn't exist.</returns> + 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; + } + + /// <summary> + /// Calls Add-AppxPackage with the specified path. + /// </summary> + /// <param name="localPath">The path of the package to add.</param> + /// <param name="downgrade">If the package version is lower than the installed one.</param> + 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(); + + StringBuilder sb = new StringBuilder(); + sb.Append(string.Format(AddAppxPackageFormat, localPath)); + + if (downgrade) + { + sb.Append(ForceUpdateFromAnyVersion); + } + + // 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); + } + + /// <summary> + /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml. + /// </summary> + public void RegisterAppInstaller() + { + string packageFullName = this.GetAppInstallerPropertyValue(PackageFullName); + string appxManifestPath = Path.Combine( + Utilities.ProgramFilesWindowsAppPath, + packageFullName, + AppxManifest); + + this.psCmdlet.InvokeCommand.InvokeScript( + string.Format(AddAppxPackageRegisterFormat, appxManifestPath)); + } + + private PSObject GetAppxObject(string packageName) + { + return this.psCmdlet.InvokeCommand + .InvokeScript(string.Format(GetAppxPackageCommand, packageName)) + .FirstOrDefault(); + } + + private IReadOnlyList<string> GetVCLibsDependencies() + { + var vcLibsDependencies = new List<string>(); + 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()); + } + } + else + { + this.psCmdlet.WriteDebug($"VCLibs are updated."); + } + + return vcLibsDependencies; + } + + private void InstallVCLibsDependencies() + { + var packages = this.GetVCLibsDependencies(); + foreach (var package in packages) + { + this.psCmdlet.WriteDebug($"Installing VCLibs {package}"); + this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, package)); + } + } + + 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(Resources.MicrosoftUIXaml27Message); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ComObjectFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ComObjectFactory.cs @@ -0,0 +1,189 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ComObjectFactory.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.IO; + using System.Reflection; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + +#if NET + using WinRT; +#endif + + /// <summary> + /// Constructs instances of classes from the <see cref="Management.Deployment" /> namespace. + /// </summary> + public class ComObjectFactory + { +#if USE_PROD_CLSIDS + private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); +#else + private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); +#endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type FindPackagesOptionsType = Type.GetTypeFromCLSID(FindPackagesOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); + + private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); + private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); + private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); + private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); + private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); + private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); + + /// <summary> + /// Initializes static members of the <see cref="ComObjectFactory"/> class. + /// </summary> + static ComObjectFactory() + { + if (Utilities.UsesInProcWinget) + { + PackageManagerSettings settings = new PackageManagerSettings(); + settings.SetCallerIdentifier("PowerShellInProc"); + } + } + + /// <summary> + /// Creates an instance of the <see cref="PackageManager" /> class. + /// </summary> + /// <returns>A <see cref="PackageManager" /> instance.</returns> + public virtual PackageManager CreatePackageManager() + { + return Create<PackageManager>(PackageManagerType, PackageManagerIid); + } + + /// <summary> + /// Creates an instance of the <see cref="FindPackagesOptions" /> class. + /// </summary> + /// <returns>A <see cref="FindPackagesOptions" /> instance.</returns> + public virtual FindPackagesOptions CreateFindPackagesOptions() + { + return Create<FindPackagesOptions>(FindPackagesOptionsType, FindPackagesOptionsIid); + } + + /// <summary> + /// Creates an instance of the <see cref="CreateCompositePackageCatalogOptions" /> class. + /// </summary> + /// <returns>A <see cref="CreateCompositePackageCatalogOptions" /> instance.</returns> + public virtual CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() + { + return Create<CreateCompositePackageCatalogOptions>(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); + } + + /// <summary> + /// Creates an instance of the <see cref="InstallOptions" /> class. + /// </summary> + /// <returns>An <see cref="InstallOptions" /> instance.</returns> + public virtual InstallOptions CreateInstallOptions() + { + return Create<InstallOptions>(InstallOptionsType, InstallOptionsIid); + } + + /// <summary> + /// Creates an instance of the <see cref="UninstallOptions" /> class. + /// </summary> + /// <returns>A <see cref="UninstallOptions" /> instance.</returns> + public virtual UninstallOptions CreateUninstallOptions() + { + return Create<UninstallOptions>(UninstallOptionsType, UninstallOptionsIid); + } + + /// <summary> + /// Creates an instance of the <see cref="PackageMatchFilter" /> class. + /// </summary> + /// <returns>A <see cref="PackageMatchFilter" /> instance.</returns> + public virtual PackageMatchFilter CreatePackageMatchFilter() + { + return Create<PackageMatchFilter>(PackageMatchFilterType, PackageMatchFilterIid); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static T Create<T>(Type type, in Guid iid) + where T : new() + { + if (Utilities.UsesInProcWinget) + { + string executingAssemblyLocation = Assembly.GetExecutingAssembly().Location; + string executingAssemblyDirectory = Path.GetDirectoryName(executingAssemblyLocation); + SetDllDirectoryW(executingAssemblyDirectory); + + try + { + return new T(); + } + finally + { + SetDllDirectoryW(null); + } + } + + object instance = null; + + if (Utilities.ExecutingAsAdministrator) + { + int hr = WinGetServerManualActivation_CreateInstance(type.GUID, iid, 0, out instance); + + if (hr < 0) + { + if (hr == ErrorCode.FileNotFound) + { + throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); + } + else + { + throw new COMException($"Failed to create instance: {hr}", hr); + } + } + } + else + { + instance = Activator.CreateInstance(type); + } + +#if NET + IntPtr pointer = Marshal.GetIUnknownForObject(instance); + return MarshalInterface<T>.FromAbi(pointer); +#else + return (T)instance; +#endif + } + + [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] + private static extern int WinGetServerManualActivation_CreateInstance( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, + [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, + uint flags, + [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetDllDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string directory); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubRelease.cs @@ -0,0 +1,127 @@ +// ----------------------------------------------------------------------------- +// <copyright file="GitHubRelease.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using Octokit; + using FileMode = System.IO.FileMode; + + /// <summary> + /// Handles WinGet's releases in GitHub. + /// </summary> + 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; + + /// <summary> + /// Initializes a new instance of the <see cref="GitHubRelease"/> class. + /// </summary> + public GitHubRelease() + { + this.gitHubClient = new GitHubClient(new ProductHeaderValue(UserAgent)); + } + + /// <summary> + /// Download a release from winget-cli. + /// </summary> + /// <param name="releaseTag">Optional release name. If null, gets latest.</param> + /// <returns>Path where the msix bundle is downloaded.</returns> + public string DownloadRelease(string releaseTag) + { + return this.DownloadReleaseAsync(releaseTag).GetAwaiter().GetResult(); + } + + /// <summary> + /// Gets the latest released version and waits. + /// </summary> + /// <param name="includePreRelease">Include prerelease.</param> + /// <returns>Latest version.</returns> + public string GetLatestVersionTagName(bool includePreRelease) + { + return this.GetLatestVersionAsync(includePreRelease).GetAwaiter().GetResult().TagName; + } + + /// <summary> + /// Downloads a file from a url and waits. + /// </summary> + /// <param name="url">Url.</param> + /// <param name="fileName">File name.</param> + public void DownloadUrl(string url, string fileName) + { + this.DownloadUrlAsync(url, fileName).GetAwaiter().GetResult(); + } + + /// <summary> + /// Download asynchronously a release from winget-cli. + /// </summary> + /// <param name="releaseTag">Optional release name. If null, gets latest.</param> + /// <returns>Path where the msix bundle is downloaded.</returns> + public async Task<string> DownloadReleaseAsync(string 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(); + + var tmpFile = Path.GetTempFileName(); + await this.DownloadUrlAsync(msixBundleAsset.Url, tmpFile); + return tmpFile; + } + + /// <summary> + /// Downloads a file from a url. + /// </summary> + /// <param name="url">Url.</param> + /// <param name="fileName">File name.</param> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> + public async Task DownloadUrlAsync(string url, string fileName) + { + var response = await this.gitHubClient.Connection.Get<object>( + new Uri(url), + new Dictionary<string, string>(), + ContentType); + + using var memoryStream = new MemoryStream((byte[])response.Body); + using var fileStream = File.Open(fileName, FileMode.Open); + memoryStream.Position = 0; + await memoryStream.CopyToAsync(fileStream); + } + + /// <summary> + /// Gets the latest released version. + /// </summary> + /// <param name="includePreRelease">Include prerelease.</param> + /// <returns>Latest version.</returns> + internal async Task<Release> 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.Engine/Helpers/PSEnumHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSEnumHelpers.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using Microsoft.Management.Deployment; + using Windows.System; + + /// <summary> + /// Extension methods for PS Enum wrappers for Microsoft.Management.Deployment enums. + /// </summary> + internal static class PSEnumHelpers + { + /// <summary> + /// Converts PSPackageInstallMode string value to PackageInstallMode. + /// </summary> + /// <param name="value">PSPackageInstallMode to string value.</param> + /// <returns>PackageInstallMode.</returns> + public static PackageInstallMode ToPackageInstallMode(string value) + { + return value switch + { + "Default" => PackageInstallMode.Default, + "Silent" => PackageInstallMode.Silent, + "Interactive" => PackageInstallMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// <summary> + /// Converts PSPackageInstallScope string value to PackageInstallScope. + /// </summary> + /// <param name="value">PSPackageInstallScope to string value.</param> + /// <returns>PackageInstallScope.</returns> + public static PackageInstallScope ToPackageInstallScope(string value) + { + return value switch + { + "Any" => PackageInstallScope.Any, + "User" => PackageInstallScope.User, + "System" => PackageInstallScope.System, + "UserOrUnknown" => PackageInstallScope.UserOrUnknown, + "SystemOrUnknown" => PackageInstallScope.SystemOrUnknown, + _ => throw new InvalidOperationException(), + }; + } + + /// <summary> + /// Converts PSProcessorArchitecture string value to ProcessorArchitecture. + /// </summary> + /// <param name="value">PSProcessorArchitecture to string value.</param> + /// <returns>ProcessorArchitecture.</returns> + public static ProcessorArchitecture ToProcessorArchitecture(string value) + { + return value switch + { + "X86" => ProcessorArchitecture.X86, + "Arm" => ProcessorArchitecture.Arm, + "X64" => ProcessorArchitecture.X64, + "Arm64" => ProcessorArchitecture.Arm64, + _ => throw new InvalidOperationException(), + }; + } + + /// <summary> + /// Converts PSPackageUninstallMode to PackageUninstallMode. + /// </summary> + /// <param name="value">PSPackageUninstallMode string value.</param> + /// <returns>PackageUninstallMode.</returns> + public static PackageUninstallMode ToPackageUninstallMode(string value) + { + return value switch + { + "Default" => PackageUninstallMode.Default, + "Silent" => PackageUninstallMode.Silent, + "Interactive" => PackageUninstallMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandResult.cs @@ -0,0 +1,75 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetCLICommandResult.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using Microsoft.WinGet.Client.Engine.Exceptions; + + /// <summary> + /// Winget cli command result. + /// </summary> + internal class WinGetCLICommandResult + { + /// <summary> + /// Initializes a new instance of the <see cref="WinGetCLICommandResult"/> class. + /// </summary> + /// <param name="command">Command.</param> + /// <param name="parameters">Parameters.</param> + /// <param name="exitCode">Exit code.</param> + /// <param name="stdOut">Standard output.</param> + /// <param name="stdErr">Standard error.</param> + public WinGetCLICommandResult(string command, string parameters, int exitCode, string stdOut, string stdErr) + { + this.Command = command; + this.Parameters = parameters; + this.ExitCode = exitCode; + this.StdOut = stdOut; + this.StdErr = stdErr; + } + + /// <summary> + /// Gets the command. + /// </summary> + public string Command { get; private set; } + + /// <summary> + /// Gets the parameters. + /// </summary> + public string Parameters { get; private set; } + + /// <summary> + /// Gets the exit code. + /// </summary> + public int ExitCode { get; private set; } + + /// <summary> + /// Gets the standard output. + /// </summary> + public string StdOut { get; private set; } + + /// <summary> + /// Gets the standard error. + /// </summary> + public string StdErr { get; private set; } + + /// <summary> + /// Verifies exit code. + /// </summary> + /// <param name="exitCode">Optional exit code.</param> + public void VerifyExitCode(int exitCode = 0) + { + if (this.ExitCode != exitCode) + { + throw new WinGetCLIException( + this.Command, + this.Parameters, + this.ExitCode, + this.StdOut, + this.StdErr); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs @@ -0,0 +1,118 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WinGetVersion.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + + /// <summary> + /// WinGetVersion. Parse the string version returned by winget --version to allow comparisons. + /// </summary> + internal class WinGetVersion + { + /// <summary> + /// Initializes a new instance of the <see cref="WinGetVersion"/> class. + /// </summary> + /// <param name="version">String Version.</param> + 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); + } + + /// <summary> + /// Gets the version of the installed winget. + /// </summary> + public static WinGetVersion InstalledWinGetVersion + { + get + { + var wingetCliWrapper = new WingetCLIWrapper(); + var result = wingetCliWrapper.RunCommand("--version"); + return new WinGetVersion(result.StdOut.Replace(Environment.NewLine, string.Empty)); + } + } + + /// <summary> + /// Gets the version as it appears as a tag. + /// </summary> + public string TagVersion { get; } + + /// <summary> + /// Gets the version. + /// </summary> + public System.Version Version { get; } + + /// <summary> + /// Gets a value indicating whether is this version is a prerelease. + /// </summary> + public bool IsPrerelease { get; } + + /// <summary> + /// Version.CompareTo taking into account prerelease. + /// From semver: Pre-release versions have a lower precedence than the associated normal version. + /// </summary> + /// <param name="otherVersion">Other winget version.</param> + /// <returns> + /// 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. + /// </returns> + 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); + } + + /// <summary> + /// Deployment doesn't care about semver or prerelease builds. + /// </summary> + /// <param name="otherVersion">Other version.</param> + /// <returns> + /// 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. + /// </returns> + public int CompareAsDeployment(WinGetVersion otherVersion) + { + return this.Version.CompareTo(otherVersion.Version); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs @@ -0,0 +1,99 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WingetCLIWrapper.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Diagnostics; + using System.IO; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + + /// <summary> + /// Calls winget directly. + /// </summary> + internal class WingetCLIWrapper + { + /// <summary> + /// The file name to use in start info. + /// </summary> + private readonly string wingetPath; + + /// <summary> + /// Initializes a new instance of the <see cref="WingetCLIWrapper"/> 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. + /// </summary> + /// <param name="fullPath">Use full path or not.</param> + public WingetCLIWrapper(bool fullPath = true) + { + if (fullPath) + { + this.wingetPath = WinGetFullPath; + } + else + { + this.wingetPath = Constants.WinGetExe; + } + } + + /// <summary> + /// Gets the full path of winget executable. + /// </summary> + public static string WinGetFullPath + { + get + { + return Path.Combine( + Utilities.LocalDataWindowsAppPath, + Constants.WingetPackageFamilyName, + Constants.WinGetExe); + } + } + + /// <summary> + /// Runs winget command with parameters. + /// </summary> + /// <param name="command">Command.</param> + /// <param name="parameters">Parameters.</param> + /// <param name="timeOut">Time out.</param> + /// <returns>WinGetCommandResult.</returns> + 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 (this.wingetPath, args) + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }, + }; + + p.Start(); + + if (p.WaitForExit(timeOut)) + { + return new WinGetCLICommandResult( + command, + parameters, + p.ExitCode, + p.StandardOutput.ReadToEnd(), + p.StandardError.ReadToEnd()); + } + + throw new WinGetCLITimeoutException(command, parameters); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WriteProgressAdapter.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WriteProgressAdapter.cs @@ -0,0 +1,90 @@ +// ----------------------------------------------------------------------------- +// <copyright file="WriteProgressAdapter.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading; + + /// <summary> + /// Marshals calls to <see cref="Cmdlet.WriteProgress(ProgressRecord)" /> back to the main thread. + /// </summary> + internal class WriteProgressAdapter + { + private readonly AutoResetEvent resetEvent = new (false); + private readonly Queue<ProgressRecord> records = new (); + private readonly Cmdlet cmdlet; + private volatile bool completed = false; + + /// <summary> + /// Initializes a new instance of the <see cref="WriteProgressAdapter" /> class. + /// </summary> + /// <param name="cmdlet">A <see cref="Cmdlet" /> instance.</param> + public WriteProgressAdapter(Cmdlet cmdlet) + { + this.cmdlet = cmdlet; + } + + /// <summary> + /// Sets a value indicating whether the asynchronous operation is finished and the main thread can continue. + /// </summary> + public bool Completed + { + set + { + this.completed = value; + if (value) + { + this.resetEvent.Set(); + } + } + } + + /// <summary> + /// This should be called on the main thread to wait for the asynchronous operation to complete. + /// </summary> + public void Wait() + { + while (!this.completed) + { + lock (this.records) + { + this.Flush(); + } + + this.resetEvent.WaitOne(); + } + + this.Flush(); + } + + /// <summary> + /// This is an analogue of the <see cref="Cmdlet.WriteProgress(ProgressRecord)" /> function. + /// </summary> + /// <param name="record">A <see cref="ProgressRecord" /> instance.</param> + public void WriteProgress(ProgressRecord record) + { + if (record != null) + { + lock (this.records) + { + this.records.Enqueue(record); + } + + this.resetEvent.Set(); + } + } + + private void Flush() + { + while (this.records.Count > 0) + { + this.cmdlet.WriteProgress(this.records.Dequeue()); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj @@ -0,0 +1,178 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <!-- If these target frameworks are updated, make sure to also update the .psd1 and .nuspec files.--> + <TargetWindowsVersion>10.0.22000.0</TargetWindowsVersion> + <CoreFramework>net6.0-windows$(TargetWindowsVersion)</CoreFramework> + <DesktopFramework>net48</DesktopFramework> + </PropertyGroup> + + <PropertyGroup> + <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> + <LangVersion>10</LangVersion> + <BuildOutputDirectory>$(SolutionDir)$(Platform)\$(Configuration)\</BuildOutputDirectory> + <OutputPath>$(BuildOutputDirectory)$(MSBuildProjectName)</OutputPath> + <Platforms>x64;x86;ARM64</Platforms> + <TargetFrameworks>$(CoreFramework);$(DesktopFramework)</TargetFrameworks> + <DocumentationFile>$(OutputPath)\Microsoft.WinGet.Client.Engine.xml</DocumentationFile> + <SupportedOSPlatformVersion>10.0.18362.0</SupportedOSPlatformVersion> + </PropertyGroup> + + <PropertyGroup Condition="'$(UseProdCLSIDs)' == 'true'"> + <DefineConstants>USE_PROD_CLSIDS</DefineConstants> + </PropertyGroup> + + <ItemGroup> + <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Octokit" Version="4.0.3" /> + <PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="all" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\Microsoft.Management.Deployment\Microsoft.Management.Deployment.vcxproj"> + <OutputItemType>Content</OutputItemType> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </ProjectReference> + <ProjectReference Include="..\..\Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj"> + <OutputItemType>Content</OutputItemType> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </ProjectReference> + <ProjectReference Include="..\..\Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.vcxproj"> + <OutputItemType>Content</OutputItemType> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </ProjectReference> + <ProjectReference Include="..\..\WindowsPackageManager\WindowsPackageManager.vcxproj"> + <OutputItemType>Content</OutputItemType> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </ProjectReference> + </ItemGroup> + + <PropertyGroup> + <!-- Disable warning for CS1591 because auto generated Microsoft.Management.Deployment --> + <!-- For our code, we still have stylecops acting on it. --> + <NoWarn>1591</NoWarn> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)'=='Release'"> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + </PropertyGroup> + + <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifiers>win10-x64;win10-x86;win10-arm;win10-arm64</RuntimeIdentifiers> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'x64' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-x64</RuntimeIdentifier> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'x86' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-x86</RuntimeIdentifier> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'arm' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-arm</RuntimeIdentifier> + </PropertyGroup> + + <PropertyGroup Condition=" '$(Platform)' == 'arm64' AND '$(TargetFramework)' == '$(CoreFramework)'"> + <RuntimeIdentifier>win10-arm64</RuntimeIdentifier> + </PropertyGroup> + + <ItemGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.5" /> + </ItemGroup> + + <ItemGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> + <PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.22000.196" PrivateAssets="all" /> + </ItemGroup> + + <ItemGroup> + <Compile Update="Properties\Resources.Designer.cs"> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Update="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + + <PropertyGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> + <DefineConstants>POWERSHELL_WINDOWS</DefineConstants> + </PropertyGroup> + + <ItemGroup> + <Compile Update="Properties\Resources.Designer.cs"> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Update="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + + <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> + <CsWinRTIncludes>Microsoft.Management.Deployment</CsWinRTIncludes> + <CsWinRTWindowsMetadata>$(TargetWindowsVersion)</CsWinRTWindowsMetadata> + <!-- Ensure Support for Windows 10, Version 1809 --> + <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> + </PropertyGroup> + + <!-- Copy unmanaged dlls that are not copied in the build server --> + <Target Name="CopyWinRTAct" AfterTargets="AfterBuild" > + <PropertyGroup> + <WinRTActDir>$(OutputPath)..\..\..\UndockedRegFreeWinRT\</WinRTActDir> + <!-- The output path for this project is different on some builds where the RIDs are not used in the output. --> + <WinRTActDir Condition="!Exists('$(WinRTActDir)winrtact.dll')">$(OutputPath)..\..\UndockedRegFreeWinRT\</WinRTActDir> + </PropertyGroup> + <ItemGroup> + <WinRTActDll Include="$(WinRTActDir)winrtact.dll" /> + </ItemGroup> + <Message Importance="high" Text="Copying '@(WinRTActDll)'" /> + <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(OutputPath)" /> + </Target> + + <Target Name="CopyDeploymentInproc" AfterTargets="AfterBuild" > + <PropertyGroup> + <DeploymentInprocDir>$(OutputPath)..\..\..\Microsoft.Management.Deployment.InProc\</DeploymentInprocDir> + <!-- The output path for this project is different on some builds where the RIDs are not used in the output. --> + <DeploymentInprocDir Condition="!Exists('$(DeploymentInprocDir)Microsoft.Management.Deployment.InProc.dll')">$(OutputPath)..\..\Microsoft.Management.Deployment.InProc\</DeploymentInprocDir> + </PropertyGroup> + <ItemGroup> + <DeploymentInprocDll Include="$(DeploymentInprocDir)Microsoft.Management.Deployment.InProc.dll" /> + </ItemGroup> + <Message Importance="high" Text="Copying '@(DeploymentInprocDll)'" /> + <Copy SourceFiles="@(DeploymentInprocDll)" DestinationFolder="$(OutputPath)" /> + </Target> + + <Target Name="CopyWindowsPackageManager" AfterTargets="AfterBuild" > + <PropertyGroup> + <WindowsPackageManagerDir>$(OutputPath)..\..\..\WindowsPackageManager\</WindowsPackageManagerDir> + <!-- The output path for this project is different on some builds where the RIDs are not used in the output. --> + <WindowsPackageManagerDir Condition="!Exists('$(WindowsPackageManagerDir)WindowsPackageManager.dll')">$(OutputPath)..\..\WindowsPackageManager\</WindowsPackageManagerDir> + </PropertyGroup> + <ItemGroup> + <WindowsPackageManagerDll Include="$(WindowsPackageManagerDir)WindowsPackageManager.dll" /> + </ItemGroup> + <Message Importance="high" Text="Copying '@(WindowsPackageManagerDll)'" /> + <Copy SourceFiles="@(WindowsPackageManagerDll)" DestinationFolder="$(OutputPath)" /> + </Target> + +</Project> diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs @@ -0,0 +1,132 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSCatalogPackage.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System.Linq; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Exceptions; + + /// <summary> + /// CatalogPackage wrapper object for displaying to PowerShell. + /// </summary> + public abstract class PSCatalogPackage + { + /// <summary> + /// Initializes a new instance of the <see cref="PSCatalogPackage"/> class. + /// </summary> + /// <param name="catalogPackage">CatalogPackage COM object.</param> + internal PSCatalogPackage(Management.Deployment.CatalogPackage catalogPackage) + { + this.CatalogPackageCOM = catalogPackage; + } + + /// <summary> + /// Gets the name of the catalog package. + /// </summary> + public string Name + { + get + { + return this.CatalogPackageCOM.Name; + } + } + + /// <summary> + /// Gets the id of the catalog package. + /// </summary> + public string Id + { + get + { + return this.CatalogPackageCOM.Id; + } + } + + /// <summary> + /// Gets a value indicating whether an update is available. + /// </summary> + public bool IsUpdateAvailable + { + get + { + return this.CatalogPackageCOM.IsUpdateAvailable; + } + } + + /// <summary> + /// Gets the source name of the catalog package. + /// </summary> + public string Source + { + get + { + return this.CatalogPackageCOM.DefaultInstallVersion.PackageCatalog.Info.Name; + } + } + + /// <summary> + /// Gets list of strings representing the available versions. + /// </summary> + public string[] AvailableVersions + { + get + { + return this.AvailablePackageVersionIds.Select(i => i.Version).ToArray(); + } + } + + /// <summary> + /// Gets the version of the catalog package. + /// </summary> + public abstract string Version { get; } + + /// <summary> + /// Gets the catalog package COM object. + /// </summary> + internal CatalogPackage CatalogPackageCOM { get; private set; } + + /// <summary> + /// Gets a list of available package version ids for the package. + /// </summary> + private PackageVersionId[] AvailablePackageVersionIds + { + get + { + return this.CatalogPackageCOM.AvailableVersions.ToArray(); + } + } + + /// <summary> + /// Checks the installed status of the catalog package. + /// </summary> + /// <returns>CheckInstalledStatus string.</returns> + public string CheckInstalledStatus() + { + return this.CatalogPackageCOM.CheckInstalledStatus().Status.ToString(); + } + + /// <summary> + /// Gets the PackageVersionInfo PSObject that corresponds with the version string. + /// </summary> + /// <param name="version">Version string.</param> + /// <returns>PackageVersionInfo PSObject.</returns> + /// <exception cref="NoPackageFoundException">Throws an exception if no package is found.</exception> + public PSPackageVersionInfo GetPackageVersionInfo(string version) + { + // get specific version that matches + PackageVersionId packageVersionId = this.AvailablePackageVersionIds.FirstOrDefault(x => x.Version == version); + if (packageVersionId != null) + { + return new PSPackageVersionInfo(this.CatalogPackageCOM.GetPackageVersionInfo(packageVersionId)); + } + else + { + throw new NoPackageFoundException(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSFoundCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSFoundCatalogPackage.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSFoundCatalogPackage.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using Microsoft.Management.Deployment; + + /// <summary> + /// FoundCatalogPackage wrapper object for displaying to PowerShell. + /// </summary> + public sealed class PSFoundCatalogPackage : PSCatalogPackage + { + /// <summary> + /// Initializes a new instance of the <see cref="PSFoundCatalogPackage"/> class. + /// </summary> + /// <param name="catalogPackage">The catalog package COM object.</param> + internal PSFoundCatalogPackage(CatalogPackage catalogPackage) + : base(catalogPackage) + { + } + + /// <inheritdoc/> + public override string Version + { + get { return this.CatalogPackageCOM.DefaultInstallVersion.Version; } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstallResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstallResult.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSInstallResult.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + + /// <summary> + /// PSInstallResult. + /// </summary> + public sealed class PSInstallResult + { + private readonly InstallResult installResult; + + /// <summary> + /// Initializes a new instance of the <see cref="PSInstallResult"/> class. + /// </summary> + /// <param name="installResult">The install result COM object.</param> + internal PSInstallResult(InstallResult installResult) + { + this.installResult = installResult; + } + + /// <summary> + /// Gets the correlation data of the install result. + /// </summary> + public string CorrelationData + { + get + { + return this.installResult.CorrelationData; + } + } + + /// <summary> + /// Gets the error code of an install. + /// </summary> + public uint InstallerErrorCode + { + get + { + return this.installResult.InstallerErrorCode; + } + } + + /// <summary> + /// Gets the extended error code exception of the failed install result. + /// </summary> + public Exception ExtendedErrorCode + { + get + { + return this.installResult.ExtendedErrorCode; + } + } + + /// <summary> + /// Gets a value indicating whether a reboot is required. + /// </summary> + public bool RebootRequired + { + get + { + return this.installResult.RebootRequired; + } + } + + /// <summary> + /// Gets the status of the install. + /// </summary> + public string Status + { + get + { + return this.installResult.Status.ToString(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSInstalledCatalogPackage.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using Microsoft.Management.Deployment; + + /// <summary> + /// InstalledCatalogPackage wrapper object for displaying to PowerShell. + /// </summary> + public sealed class PSInstalledCatalogPackage : PSCatalogPackage + { + /// <summary> + /// Initializes a new instance of the <see cref="PSInstalledCatalogPackage"/> class. + /// </summary> + /// <param name="catalogPackage">The catalog package COM object.</param> + internal PSInstalledCatalogPackage(CatalogPackage catalogPackage) + : base(catalogPackage) + { + } + + /// <inheritdoc/> + public override string Version + { + get { return this.CatalogPackageCOM.InstalledVersion.Version; } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackageVersionInfo.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackageVersionInfo.cs @@ -0,0 +1,104 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSPackageVersionInfo.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System.Linq; + using Microsoft.Management.Deployment; + + /// <summary> + /// PackageVersionInfo wrapper object for displaying to PowerShell. + /// </summary> + public sealed class PSPackageVersionInfo + { + private PackageVersionInfo packageVersionInfo; + + /// <summary> + /// Initializes a new instance of the <see cref="PSPackageVersionInfo"/> class. + /// </summary> + /// <param name="packageVersionInfo">PackageVersionInfo COM object.</param> + internal PSPackageVersionInfo(PackageVersionInfo packageVersionInfo) + { + this.packageVersionInfo = packageVersionInfo; + } + + /// <summary> + /// Gets the name of the package version info. + /// </summary> + public string DisplayName + { + get + { + return this.packageVersionInfo.DisplayName; + } + } + + /// <summary> + /// Gets the id of the package version info. + /// </summary> + public string Id + { + get + { + return this.packageVersionInfo.Id; + } + } + + /// <summary> + /// Gets the publisher of the package version info. + /// </summary> + public string Publisher + { + get + { + return this.packageVersionInfo.Publisher; + } + } + + /// <summary> + /// Gets the channel of the package version info. + /// </summary> + public string Channel + { + get + { + return this.packageVersionInfo.Channel; + } + } + + /// <summary> + /// Gets the list of package family names of the package version info. + /// </summary> + public string[] PackageFamilyNames + { + get + { + return this.packageVersionInfo.PackageFamilyNames.ToArray(); + } + } + + /// <summary> + /// Gets the list of product codes of the package version info. + /// </summary> + public string[] ProductCodes + { + get + { + return this.packageVersionInfo.ProductCodes.ToArray(); + } + } + + /// <summary> + /// Compares the version string with the package version info and returns the CompareResult. + /// </summary> + /// <param name="version">Version string.</param> + /// <returns>CompareResult string.</returns> + public string CompareToVersion(string version) + { + return this.packageVersionInfo.CompareToVersion(version).ToString(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSSourceResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSSourceResult.cs @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSSourceResult.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// <summary> + /// SourceResult wrapper object for displaying to PowerShell. + /// </summary> + public class PSSourceResult + { + /// <summary> + /// Initializes a new instance of the <see cref="PSSourceResult"/> class. + /// </summary> + /// <param name="catalogReference">The PackageCatalogReference COM object.</param> + internal PSSourceResult(Management.Deployment.PackageCatalogReference catalogReference) + { + var info = catalogReference.Info; + this.Name = info.Name; + this.Argument = info.Argument; + this.Type = info.Type; + } + + /// <summary> + /// Gets the name of the source. + /// </summary> + public string Name { get; private set; } + + /// <summary> + /// Gets the argument of the source. + /// </summary> + public string Argument { get; private set; } + + /// <summary> + /// Gets the type of the source. + /// </summary> + public string Type { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSUninstallResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSUninstallResult.cs @@ -0,0 +1,82 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSUninstallResult.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + + /// <summary> + /// UninstallResult wrapper object for displaying to PowerShell. + /// </summary> + public sealed class PSUninstallResult + { + private Management.Deployment.UninstallResult uninstallResult; + + /// <summary> + /// Initializes a new instance of the <see cref="PSUninstallResult"/> class. + /// </summary> + /// <param name="uninstallResult">The uninstall result COM object.</param> + internal PSUninstallResult(Management.Deployment.UninstallResult uninstallResult) + { + this.uninstallResult = uninstallResult; + } + + /// <summary> + /// Gets the correlation data of the uninstall result. + /// </summary> + public string CorrelationData + { + get + { + return this.uninstallResult.CorrelationData; + } + } + + /// <summary> + /// Gets the extended error code exception of the failed uninstall result. + /// </summary> + public Exception ExtendedErrorCode + { + get + { + return this.uninstallResult.ExtendedErrorCode; + } + } + + /// <summary> + /// Gets a value indicating whether a reboot is required. + /// </summary> + public bool RebootRequired + { + get + { + return this.uninstallResult.RebootRequired; + } + } + + /// <summary> + /// Gets the status of the uninstall. + /// </summary> + public string Status + { + get + { + return this.uninstallResult.Status.ToString(); + } + } + + /// <summary> + /// Gets the error code of an uninstall. + /// </summary> + public uint UninstallerErrorCode + { + get + { + return this.uninstallResult.UninstallerErrorCode; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -0,0 +1,288 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace Microsoft.WinGet.Client.Engine.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.Client.Engine.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// 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.. + /// </summary> + internal static string AppExecutionAliasDisabledHelpMessage { + get { + return ResourceManager.GetString("AppExecutionAliasDisabledHelpMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An error occurred while connecting to the catalog.. + /// </summary> + internal static string CatalogConnectExceptionMessage { + get { + return ResourceManager.GetString("CatalogConnectExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to An error occurred while searching for packages: {0}. + /// </summary> + internal static string FindPackagesExceptionMessage { + get { + return ResourceManager.GetString("FindPackagesExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled.. + /// </summary> + internal static string IntegrityAppExecutionAliasDisabledMessage { + get { + return ResourceManager.GetString("IntegrityAppExecutionAliasDisabledMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The App Installer is not installed.. + /// </summary> + internal static string IntegrityAppInstallerNotInstalledMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotInstalledMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The App Installer is not registered.. + /// </summary> + internal static string IntegrityAppInstallerNotRegisteredMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotRegisteredMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The App Installer does not contain the Windows Package Manager.. + /// </summary> + internal static string IntegrityAppInstallerNotSupportedMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotSupportedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Windows Package Manager returned an unexcepted result.. + /// </summary> + internal static string IntegrityFailureMessage { + get { + return ResourceManager.GetString("IntegrityFailureMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The App Installer did not automatically add the PATH environment variable.. + /// </summary> + internal static string IntegrityNotInPathMessage { + get { + return ResourceManager.GetString("IntegrityNotInPathMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later.. + /// </summary> + internal static string IntegrityOsNotSupportedMessage { + get { + return ResourceManager.GetString("IntegrityOsNotSupportedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}'. + /// </summary> + internal static string IntegrityUnexpectedVersionMessage { + get { + return ResourceManager.GetString("IntegrityUnexpectedVersionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Unable to execute winget command.. + /// </summary> + internal static string IntegrityUnknownMessage { + get { + return ResourceManager.GetString("IntegrityUnknownMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No source matches the given value: {0}. + /// </summary> + internal static string InvalidSourceExceptionMessage { + get { + return ResourceManager.GetString("InvalidSourceExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No versions matched the given value: {0}. + /// </summary> + internal static string InvalidVersionExceptionMessage { + get { + return ResourceManager.GetString("InvalidVersionExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Microsoft.UI.Xaml.2.7 package is not installed. + /// </summary> + internal static string MicrosoftUIXaml27Message { + get { + return ResourceManager.GetString("MicrosoftUIXaml27Message", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No packages matched the given input criteria.. + /// </summary> + internal static string NoPackageFoundExceptionMessage { + get { + return ResourceManager.GetString("NoPackageFoundExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Installing '{0}'. + /// </summary> + internal static string ProgressRecordActivityInstalling { + get { + return ResourceManager.GetString("ProgressRecordActivityInstalling", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Uninstalling '{0}'. + /// </summary> + internal static string ProgressRecordActivityUninstalling { + get { + return ResourceManager.GetString("ProgressRecordActivityUninstalling", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Updating '{0}'. + /// </summary> + internal static string ProgressRecordActivityUpdating { + get { + return ResourceManager.GetString("ProgressRecordActivityUpdating", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA).. + /// </summary> + internal static string SingleThreadedApartmentNotSupportedMessage { + get { + return ResourceManager.GetString("SingleThreadedApartmentNotSupportedMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to User settings file is invalid.. + /// </summary> + internal static string UserSettingsReadException { + get { + return ResourceManager.GetString("UserSettingsReadException", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to {0}, {1}, and {2} other packages matched the input criteria. Please refine the input.. + /// </summary> + internal static string VagueCriteriaExceptionMessage { + get { + return ResourceManager.GetString("VagueCriteriaExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Command {0} failed with exit code {1}. + /// </summary> + internal static string WinGetCLIExceptionMessage { + get { + return ResourceManager.GetString("WinGetCLIExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Winget command run timed out: {0} {1}. + /// </summary> + internal static string WinGetCLITimeoutExceptionMessage { + get { + return ResourceManager.GetString("WinGetCLITimeoutExceptionMessage", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Windows Package Manager not supported.. + /// </summary> + 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.Engine/Properties/Resources.resx diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertWinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/AssertWinGetPackageManagerCommand.cs @@ -1,43 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="AssertWinGetPackageManagerCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Helpers; - - /// <summary> - /// Assert-WinGetPackageManager. Verifies winget is installed properly. - /// </summary> - [Cmdlet( - VerbsLifecycle.Assert, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - public class AssertWinGetPackageManagerCommand : BaseIntegrityCommand - { - /// <summary> - /// Validates winget is installed correctly. If not, throws an exception - /// with the reason why, if any. - /// </summary> - protected override void ProcessRecord() - { - string expectedVersion = string.Empty; - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - var gitHubRelease = new GitHubRelease(); - expectedVersion = gitHubRelease.GetLatestVersionTagName(this.IncludePreRelease.ToBool()); - } - else - { - expectedVersion = this.Version; - } - - WinGetIntegrity.AssertWinGet(this, expectedVersion); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseClientCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseClientCommand.cs @@ -1,61 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseClientCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Exceptions; - using Microsoft.WinGet.Client.Factories; - - /// <summary> - /// This is the base class for all of the commands in this module that use the COM APIs. - /// </summary> - public abstract class BaseClientCommand : BaseCommand - { - static BaseClientCommand() - { - InitializeUndockedRegFreeWinRT(); - } - - /// <summary> - /// Gets the instance of the <see cref="ComObjectFactory" /> class. - /// </summary> - protected static Lazy<ComObjectFactory> ComObjectFactory { get; } = new (); - - /// <summary> - /// Gets the instance of the <see cref="PackageManager" /> class. - /// </summary> - protected static Lazy<PackageManager> PackageManager { get; } = new (() => ComObjectFactory.Value.CreatePackageManager()); - - /// <summary> - /// Retrieves the specified source or all sources if <paramref name="source" /> is null. - /// </summary> - /// <returns>A list of <see cref="PackageCatalogReference" /> instances.</returns> - /// <param name="source">The name of the source to retrieve. If null, then all sources are returned.</param> - /// <exception cref="ArgumentException">The source does not exist.</exception> - protected static IReadOnlyList<PackageCatalogReference> GetPackageCatalogReferences(string source) - { - if (source is null) - { - return PackageManager.Value.GetPackageCatalogs(); - } - else - { - return new List<PackageCatalogReference>() - { - PackageManager.Value.GetPackageCatalogByName(source) - ?? throw new InvalidSourceException(source), - }; - } - } - - [DllImport("winrtact.dll", EntryPoint = "winrtact_Initialize", ExactSpelling = true, PreserveSig = true)] - private static extern void InitializeUndockedRegFreeWinRT(); - } -}- \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseCommand.cs @@ -1,31 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Exceptions; - - /// <summary> - /// Base class for all Cmdlets. - /// </summary> - public abstract class BaseCommand : PSCmdlet - { - /// <summary> - /// Initializes a new instance of the <see cref="BaseCommand"/> class. - /// </summary> - public BaseCommand() - : base() - { - // The inproc COM API may deadlock on an STA thread. - if (Utilities.UsesInProcWinget && Utilities.ThreadIsSTA) - { - throw new SingleThreadedApartmentException(); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseFinderCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseFinderCommand.cs @@ -1,212 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseFinderCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using System.Reflection; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Attributes; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Exceptions; - - /// <summary> - /// This is the base class for all commands that might need to search for a package. It contains an initial - /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. - /// </summary> - public abstract class BaseFinderCommand : BaseClientCommand - { - /// <summary> - /// Gets or sets the field that is matched against the identifier of a package. - /// </summary> - [Filter(Field = PackageMatchField.Id)] - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Id { get; set; } - - /// <summary> - /// Gets or sets the field that is matched against the name of a package. - /// </summary> - [Filter(Field = PackageMatchField.Name)] - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the field that is matched against the name of a package. - /// </summary> - [Filter(Field = PackageMatchField.Moniker)] - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Moniker { get; set; } - - /// <summary> - /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. - /// </summary> - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Source { get; set; } - - /// <summary> - /// Gets or sets the strings that match against every field of a package. - /// </summary> - [Parameter( - ParameterSetName = Constants.FoundSet, - Position = 0, - ValueFromPipelineByPropertyName = true, - ValueFromRemainingArguments = true)] - public string[] Query { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether to match exactly against package fields. - /// </summary> - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter Exact { get; set; } - - private string QueryAsJoinedString - { - get - { - return this.Query is null - ? null - : string.Join(" ", this.Query); - } - } - - /// <summary> - /// Returns a <see cref="PackageFieldMatchOption" /> based on a parameter. - /// </summary> - /// <returns>A <see cref="PackageFieldMatchOption" /> value.</returns> - protected virtual PackageFieldMatchOption GetExactAsMatchOption() - { - return this.Exact.ToBool() - ? PackageFieldMatchOption.Equals - : PackageFieldMatchOption.ContainsCaseInsensitive; - } - - /// <summary> - /// Searches for packages based on the configured parameters. - /// </summary> - /// <param name="behavior">The <see cref="CompositeSearchBehavior" /> value.</param> - /// <param name="limit">The limit on the number of matches returned.</param> - /// <returns>A list of <see cref="MatchResult" /> objects.</returns> - protected IReadOnlyList<MatchResult> FindPackages( - CompositeSearchBehavior behavior, - uint limit) - { - PackageCatalog catalog = this.GetPackageCatalog(behavior); - FindPackagesOptions options = this.GetFindPackagesOptions(limit); - return GetMatchResults(catalog, options); - } - - private static void SetQueryInFindPackagesOptions( - ref FindPackagesOptions options, - PackageFieldMatchOption match, - string value) - { - var selector = ComObjectFactory.Value.CreatePackageMatchFilter(); - selector.Field = PackageMatchField.CatalogDefault; - selector.Value = value ?? string.Empty; - selector.Option = match; - options.Selectors.Add(selector); - } - - private static void AddFilterToFindPackagesOptionsIfNotNull( - ref FindPackagesOptions options, - PackageMatchField field, - PackageFieldMatchOption match, - string value) - { - if (value != null) - { - var filter = ComObjectFactory.Value.CreatePackageMatchFilter(); - filter.Field = field; - filter.Value = value; - filter.Option = match; - options.Filters.Add(filter); - } - } - - private static IReadOnlyList<MatchResult> GetMatchResults( - PackageCatalog catalog, - FindPackagesOptions options) - { - FindPackagesResult result = catalog.FindPackages(options); - if (result.Status == FindPackagesResultStatus.Ok) - { - return result.Matches; - } - else - { - throw new FindPackagesException(result.Status); - } - } - - private PackageCatalog GetPackageCatalog(CompositeSearchBehavior behavior) - { - PackageCatalogReference reference = this.GetPackageCatalogReference(behavior); - ConnectResult result = reference.Connect(); - if (result.Status == ConnectResultStatus.Ok) - { - return result.PackageCatalog; - } - else - { - throw new CatalogConnectException(); - } - } - - private PackageCatalogReference GetPackageCatalogReference(CompositeSearchBehavior behavior) - { - CreateCompositePackageCatalogOptions options = ComObjectFactory.Value.CreateCreateCompositePackageCatalogOptions(); - IReadOnlyList<PackageCatalogReference> references = GetPackageCatalogReferences(this.Source); - for (var i = 0; i < references.Count; i++) - { - options.Catalogs.Add(references[i]); - } - - options.CompositeSearchBehavior = behavior; - return PackageManager.Value.CreateCompositePackageCatalog(options); - } - - private FindPackagesOptions GetFindPackagesOptions(uint limit) - { - var options = ComObjectFactory.Value.CreateFindPackagesOptions(); - SetQueryInFindPackagesOptions(ref options, this.GetExactAsMatchOption(), this.QueryAsJoinedString); - this.AddAttributedFiltersToFindPackagesOptions(ref options, this.GetExactAsMatchOption()); - options.ResultLimit = limit; - return options; - } - - private void AddAttributedFiltersToFindPackagesOptions( - ref FindPackagesOptions options, - PackageFieldMatchOption match) - { - IEnumerable<PropertyInfo> properties = this.GetType() - .GetProperties() - .Where(property => Attribute.IsDefined(property, typeof(FilterAttribute))); - - foreach (PropertyInfo info in properties) - { - if (info.GetCustomAttribute(typeof(FilterAttribute), true) is FilterAttribute attribute) - { - PackageMatchField field = attribute.Field; - string value = info.GetValue(this, null) as string; - AddFilterToFindPackagesOptionsIfNotNull(ref options, field, match, value); - } - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseFinderExtendedCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseFinderExtendedCommand.cs @@ -1,59 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseFinderExtendedCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Attributes; - using Microsoft.WinGet.Client.Common; - - /// <summary> - /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., - /// the "search" and "list" commands. This class contains an extended set of parameters suited for - /// that purpose. - /// </summary> - public abstract class BaseFinderExtendedCommand : BaseFinderCommand - { - /// <summary> - /// Gets or sets the filter that is matched against the tags of the package. - /// </summary> - [Filter(Field = PackageMatchField.Tag)] - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Tag { get; set; } - - /// <summary> - /// Gets or sets the filter that is matched against the commands of the package. - /// </summary> - [Filter(Field = PackageMatchField.Command)] - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Command { get; set; } - - /// <summary> - /// Gets or sets the maximum number of results returned. - /// </summary> - [ValidateRange(Constants.CountLowerBound, Constants.CountUpperBound)] - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public uint Count { get; set; } - - /// <summary> - /// Searches for packages from configured sources. - /// </summary> - /// <param name="behavior">A <see cref="CompositeSearchBehavior" /> value.</param> - /// <returns>A list of <see cref="MatchResult" /> objects.</returns> - protected IReadOnlyList<MatchResult> FindPackages(CompositeSearchBehavior behavior) - { - return this.FindPackages(behavior, this.Count); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseInstallCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseInstallCommand.cs @@ -1,165 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseInstallCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Helpers; - using Windows.Foundation; - - /// <summary> - /// This is the base class for all commands that parse a <see cref="FindPackagesOptions" /> result - /// from the provided parameters i.e., the "install" and "upgrade" commands. - /// </summary> - public abstract class BaseInstallCommand : BasePackageCommand - { - private string location; - - /// <summary> - /// Gets or sets the mode to manipulate the package with. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public PackageInstallMode Mode { get; set; } = PackageInstallMode.Default; - - /// <summary> - /// Gets or sets the override arguments to be passed on to the installer. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Override { get; set; } - - /// <summary> - /// Gets or sets the arguments to be passed on to the installer in addition to the defaults. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Custom { get; set; } - - /// <summary> - /// Gets or sets the installation location. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Location - { - get => this.location; - set - { - this.location = Path.IsPathRooted(value) - ? value - : this.SessionState.Path.CurrentFileSystemLocation + @"\" + value; - } - } - - /// <summary> - /// Gets or sets a value indicating whether to skip the installer hash validation check. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AllowHashMismatch { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether to continue upon non security related failures. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// <summary> - /// Gets or sets the optional HTTP Header to pass on to the REST Source. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Header { get; set; } - - /// <summary> - /// Gets the install options from the configured parameters. - /// </summary> - /// <param name="version">The <see cref="PackageVersionId" /> to install.</param> - /// <returns>An <see cref="InstallOptions" /> instance.</returns> - protected virtual InstallOptions GetInstallOptions(PackageVersionId version) - { - InstallOptions options = ComObjectFactory.Value.CreateInstallOptions(); - options.AllowHashMismatch = this.AllowHashMismatch.ToBool(); - options.Force = this.Force.ToBool(); - options.PackageInstallMode = this.Mode; - if (version != null) - { - options.PackageVersionId = version; - } - - if (this.Log != null) - { - options.LogOutputPath = this.Log; - } - - if (this.Override != null) - { - options.ReplacementInstallerArguments = this.Override; - } - - // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if they are whitespace - if (!string.IsNullOrWhiteSpace(this.Custom)) - { - options.AdditionalInstallerArguments = this.Custom; - } - - if (this.Location != null) - { - options.PreferredInstallLocation = this.Location; - } - - if (this.Header != null) - { - options.AdditionalPackageCatalogArguments = this.Header; - } - - return options; - } - - /// <summary> - /// Registers callbacks on an asynchronous operation and waits for the results. - /// </summary> - /// <param name="operation">The asynchronous operation.</param> - /// <param name="activity">A <see cref="string" /> instance.</param> - /// <returns>A <see cref="InstallResult" /> instance.</returns> - protected InstallResult RegisterCallbacksAndWait( - IAsyncOperationWithProgress<InstallResult, InstallProgress> operation, - string activity) - { - WriteProgressAdapter adapter = new (this); - operation.Progress = (context, progress) => - { - ProgressRecord record = new (1, activity, progress.State.ToString()) - { - RecordType = ProgressRecordType.Processing, - }; - - if (progress.State == PackageInstallProgressState.Downloading && progress.BytesRequired != 0) - { - record.StatusDescription = $"{progress.BytesDownloaded / 1000000.0f:0.0} MB / {progress.BytesRequired / 1000000.0f:0.0} MB"; - record.PercentComplete = (int)(progress.DownloadProgress * 100); - } - else if (progress.State == PackageInstallProgressState.Installing) - { - record.PercentComplete = (int)(progress.InstallationProgress * 100); - } - - adapter.WriteProgress(record); - }; - operation.Completed = (context, status) => - { - adapter.WriteProgress(new ProgressRecord(1, activity, status.ToString()) - { - RecordType = ProgressRecordType.Completed, - }); - adapter.Completed = true; - }; - System.Console.CancelKeyPress += (sender, e) => - { - operation.Cancel(); - }; - adapter.Wait(); - return operation.GetResults(); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseIntegrityCommand.cs @@ -1,41 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseIntegrityCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - - /// <summary> - /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. - /// </summary> - public abstract class BaseIntegrityCommand : BaseCommand - { - /// <summary> - /// Gets or sets the optional version. - /// </summary> - [Parameter( - ParameterSetName = Constants.IntegrityVersionSet, - ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } = string.Empty; - - /// <summary> - /// Gets or sets a value indicating whether to use latest. - /// </summary> - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter Latest { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether to include prerelease winget versions. - /// </summary> - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludePreRelease { get; set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BasePackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BasePackageCommand.cs @@ -1,134 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BasePackageCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Exceptions; - - /// <summary> - /// This is the base class for commands which operate on a specific package and version i.e., - /// the "install", "uninstall", and "upgrade" commands. - /// </summary> - public abstract class BasePackageCommand : BaseFinderCommand - { - private string log; - - /// <summary> - /// Gets or sets the package to directly install. - /// </summary> - /// <remarks> - /// Must match the name of the <see cref="CatalogPackage" /> field on the <see cref="MatchResult" /> class. - /// </remarks> - [Alias("InputObject")] - [ValidateNotNull] - [Parameter( - ParameterSetName = Constants.GivenSet, - Position = 0, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public CatalogPackage CatalogPackage { get; set; } - - /// <summary> - /// Gets or sets the version to install. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } - - /// <summary> - /// Gets or sets the path to the logging file. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Log - { - get => this.log; - set - { - this.log = Path.IsPathRooted(value) - ? value - : this.SessionState.Path.CurrentFileSystemLocation + @"\" + value; - } - } - - /// <inheritdoc /> - protected override PackageFieldMatchOption GetExactAsMatchOption() - { - return this.Exact.ToBool() - ? PackageFieldMatchOption.Equals - : PackageFieldMatchOption.EqualsCaseInsensitive; - } - - /// <summary> - /// Executes a command targeting a specific package version. - /// </summary> - /// <param name="behavior">The <see cref="CompositeSearchBehavior" /> value.</param> - /// <param name="callback">The method to call after retrieving the package and version to operate upon.</param> - protected void GetPackageAndExecute( - CompositeSearchBehavior behavior, - Action<CatalogPackage, PackageVersionId> callback) - { - CatalogPackage package = this.GetCatalogPackage(behavior); - PackageVersionId version = this.GetPackageVersionId(package); - if (this.ShouldProcess(package.ToString(version))) - { - callback(package, version); - } - } - - private CatalogPackage GetCatalogPackage(CompositeSearchBehavior behavior) - { - if (this.ParameterSetName == Constants.GivenSet) - { - // The package was already provided via a parameter or the pipeline. - return this.CatalogPackage; - } - else - { - IReadOnlyList<MatchResult> results = this.FindPackages(behavior, 0); - if (results.Count == 1) - { - // Exactly one package matched, so we can just return it. - return results[0].CatalogPackage; - } - else if (results.Count == 0) - { - // No packages matched, we need to throw an error. - throw new NoPackageFoundException(); - } - else - { - // Too many packages matched! The user needs to refine their input. - throw new VagueCriteriaException(results); - } - } - } - - private PackageVersionId GetPackageVersionId(CatalogPackage package) - { - if (this.Version != null) - { - for (var i = 0; i < package.AvailableVersions.Count; i++) - { - if (package.AvailableVersions[i].Version.CompareTo(this.Version) == 0) - { - return package.AvailableVersions[i]; - } - } - - throw new InvalidVersionException(this.Version); - } - else - { - return null; - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseUserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/Common/BaseUserSettingsCommand.cs @@ -1,133 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="BaseUserSettingsCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System; - using System.Collections; - using System.IO; - using System.Management.Automation; - using Microsoft.PowerShell.Commands; - using Microsoft.WinGet.Client.Exceptions; - using Microsoft.WinGet.Client.Helpers; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - /// <summary> - /// Base command for user settings cmdlets. - /// </summary> - public abstract class BaseUserSettingsCommand : BaseCommand - { - /// <summary> - /// The schema key. - /// </summary> - protected const string SchemaKey = "$schema"; - - /// <summary> - /// The default value of the schema property. - /// </summary> - protected const string SchemaValue = "https://aka.ms/winget-settings.schema.json"; - - /// <summary> - /// Gets the path for the winget settings. - /// </summary> - protected static string WinGetSettingsFilePath - { - get - { - return GetUserSettingsPath(); - } - } - - /// <summary> - /// Converts a Hashtable to a JObject object. - /// </summary> - /// <param name="hashtable">Hashtable.</param> - /// <returns>JObject.</returns> - protected static JObject HashtableToJObject(Hashtable hashtable) - { - return (JObject)JToken.FromObject(hashtable); - } - - /// <summary> - /// Returns the contents of the settings file as Hashtable. - /// </summary> - /// <returns>Contents of settings file.</returns> - protected Hashtable GetLocalSettingsAsHashtable() - { - var content = File.Exists(WinGetSettingsFilePath) ? - File.ReadAllText(WinGetSettingsFilePath) : - string.Empty; - - return this.ConvertToHashtable(content); - } - - /// <summary> - /// Converts the current local settings file into a JObject object. - /// </summary> - /// <returns>User settings as JObject.</returns> - protected JObject LocalSettingsFileToJObject() - { - try - { - return File.Exists(WinGetSettingsFilePath) ? - JObject.Parse(File.ReadAllText(WinGetSettingsFilePath)) : - new JObject(); - } - catch (JsonReaderException e) - { - this.WriteDebug(e.Message); - throw new UserSettingsReadException(e); - } - } - - /// <summary> - /// Uses Powershell ConvertFrom-Json to convert a JSON to a Hashtable. - /// </summary> - /// <param name="content">Content.</param> - /// <returns>Hashtable.</returns> - protected Hashtable ConvertToHashtable(string content) - { - if (string.IsNullOrEmpty(content)) - { - return new Hashtable(); - } - -#if POWERSHELL_WINDOWS - throw new PSNotImplementedException(); -#else - // Powershell's documentation says that the object being return is either a PSObject - // or a Hashtable depending on the value of returnHashtable, but is not true. The return - // type is a PSObject and the BaseObject is either a OrderedHashtable or a PSCustomObject - // depending on returnHashtable. - try - { - var result = JsonObject.ConvertFromJson(content, returnHashtable: true, out ErrorRecord error) as PSObject; - if (error is not null) - { - throw new UserSettingsReadException(error.Exception); - } - - return result.BaseObject as Hashtable; - } - catch (Exception e) - { - throw new UserSettingsReadException(e); - } -#endif - } - - private static string GetUserSettingsPath() - { - var wingetCliWrapper = new WingetCLIWrapper(); - var settingsResult = wingetCliWrapper.RunCommand("settings", "export"); - - // Read the user settings file property. - var serialized = JObject.Parse(settingsResult.StdOut); - return (string)serialized.GetValue("userSettingsFile"); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/FindPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/FindPackageCommand.cs @@ -1,34 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="FindPackageCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - - /// <summary> - /// Searches configured sources for packages. - /// </summary> - [Cmdlet(VerbsCommon.Find, Constants.WinGetNouns.Package)] - [OutputType(typeof(PSObjects.FoundCatalogPackage))] - public sealed class FindPackageCommand : BaseFinderExtendedCommand - { - /// <summary> - /// Searches for configured sources for packages. - /// </summary> - protected override void ProcessRecord() - { - base.ProcessRecord(); - var results = this.FindPackages(CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs); - for (var i = 0; i < results.Count; i++) - { - this.WriteObject(new PSObjects.FoundCatalogPackage(results[i].CatalogPackage)); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetPackageCommand.cs @@ -1,34 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="GetPackageCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - - /// <summary> - /// Searches configured sources for packages. - /// </summary> - [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Package)] - [OutputType(typeof(PSObjects.InstalledCatalogPackage))] - public sealed class GetPackageCommand : BaseFinderExtendedCommand - { - /// <summary> - /// Searches for configured sources for packages. - /// </summary> - protected override void ProcessRecord() - { - base.ProcessRecord(); - var results = this.FindPackages(CompositeSearchBehavior.LocalCatalogs); - for (var i = 0; i < results.Count; i++) - { - this.WriteObject(new PSObjects.InstalledCatalogPackage(results[i].CatalogPackage)); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetSourceCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetSourceCommand.cs @@ -1,42 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="GetSourceCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - - /// <summary> - /// Retrieves the list of configured sources. - /// </summary> - [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Source)] - [OutputType(typeof(PSObjects.SourceResult))] - public sealed class GetSourceCommand : BaseClientCommand - { - /// <summary> - /// Gets or sets the name of the source to retrieve. - /// </summary> - [Parameter( - Position = 0, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// <summary> - /// Returns the list of configured sources. - /// </summary> - protected override void ProcessRecord() - { - base.ProcessRecord(); - var results = GetPackageCatalogReferences(this.Name); - for (var i = 0; i < results.Count; i++) - { - this.WriteObject(new PSObjects.SourceResult(results[i])); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetUserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetUserSettingsCommand.cs @@ -1,29 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="GetUserSettingsCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Collections; - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - - /// <summary> - /// Gets winget's user settings. - /// </summary> - [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.UserSettings)] - [OutputType(typeof(Hashtable))] - public sealed class GetUserSettingsCommand : BaseUserSettingsCommand - { - /// <summary> - /// Writes the settings file contents. - /// </summary> - protected override void ProcessRecord() - { - this.WriteObject(this.GetLocalSettingsAsHashtable()); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetVersionCommand.cs @@ -1,29 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="GetVersionCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Helpers; - - /// <summary> - /// Get-WinGetVersion. Gets the current version of winget. - /// </summary> - [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] - [OutputType(typeof(string))] - public class GetVersionCommand : BaseCommand - { - /// <summary> - /// Writes the winget version. - /// </summary> - protected override void ProcessRecord() - { - this.WriteObject(WinGetVersion.InstalledWinGetVersion.TagVersion); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/InstallPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/InstallPackageCommand.cs @@ -1,98 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="InstallPackageCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Properties; - using Windows.System; - - /// <summary> - /// Installs a package from the pipeline or from a configured source. - /// </summary> - [Cmdlet( - VerbsLifecycle.Install, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [OutputType(typeof(PSObjects.InstallResult))] - public sealed class InstallPackageCommand : BaseInstallCommand - { - private ProcessorArchitecture architecture; - private bool architectureGiven; - - /// <summary> - /// Gets or sets the scope to install the application under. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public PackageInstallScope Scope { get; set; } - - /// <summary> - /// Gets or sets the architecture of the application to be installed. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public ProcessorArchitecture Architecture - { - get => this.architecture; - set - { - this.architectureGiven = true; - this.architecture = value; - } - } - - /// <summary> - /// Prepares the command to install a package. - /// </summary> - protected override void BeginProcessing() - { - base.BeginProcessing(); - this.architectureGiven = false; - } - - /// <summary> - /// Installs a package from the pipeline or from a configured source. - /// </summary> - protected override void ProcessRecord() - { - base.ProcessRecord(); - this.GetPackageAndExecute(CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, (package, version) => - { - InstallOptions options = this.GetInstallOptions(version); - InstallResult result = this.InstallPackage(package, options); - this.WriteObject(new PSObjects.InstallResult(result)); - }); - } - - /// <inheritdoc /> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Targets Windows 10.0.17763.0")] - protected override InstallOptions GetInstallOptions(PackageVersionId version) - { - InstallOptions options = base.GetInstallOptions(version); - if (this.architectureGiven) - { - options.AllowedArchitectures.Clear(); - options.AllowedArchitectures.Add(this.architecture); - } - - options.PackageInstallScope = this.Scope; - return options; - } - - private InstallResult InstallPackage( - CatalogPackage package, - InstallOptions options) - { - var operation = PackageManager.Value.InstallPackageAsync(package, options); - return this.RegisterCallbacksAndWait(operation, string.Format( - Resources.ProgressRecordActivityInstalling, - package.Name)); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairWinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/RepairWinGetPackageManagerCommand.cs @@ -1,183 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="RepairWinGetPackageManagerCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Helpers; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Repair-WinGetPackageManager. Repairs winget if needed. - /// </summary> - [Cmdlet( - VerbsDiagnostic.Repair, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [OutputType(typeof(int))] - public class RepairWinGetPackageManagerCommand : BaseIntegrityCommand - { - private const string EnvPath = "env:PATH"; - private const int Succeeded = 0; - private const int Failed = -1; - - private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; - - /// <summary> - /// Attempts to repair winget. - /// TODO: consider WhatIf and Confirm options. - /// </summary> - protected override void ProcessRecord() - { - int result = Failed; - - string expectedVersion = this.Version; - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - var gitHubRelease = new GitHubRelease(); - expectedVersion = gitHubRelease.GetLatestVersionTagName(this.IncludePreRelease.ToBool()); - } - - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, expectedVersion); - this.WriteDebug($"Integrity category type: {integrityCategory}"); - - if (integrityCategory == IntegrityCategory.Installed || - integrityCategory == IntegrityCategory.UnexpectedVersion) - { - result = this.VerifyWinGetInstall(integrityCategory, expectedVersion); - } - else if (integrityCategory == IntegrityCategory.NotInPath) - { - this.RepairEnvPath(); - - // Now try again and get the desired winget version if needed. - var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this, expectedVersion); - this.WriteDebug($"Integrity category after fixing PATH {newIntegrityCategory}"); - result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); - } - else if (integrityCategory == IntegrityCategory.AppInstallerNotRegistered) - { - var appxModule = new AppxModuleHelper(this); - appxModule.RegisterAppInstaller(); - - // Now try again and get the desired winget version if needed. - var newIntegrityCategory = WinGetIntegrity.GetIntegrityCategory(this, expectedVersion); - this.WriteDebug($"Integrity category after registering {newIntegrityCategory}"); - result = this.VerifyWinGetInstall(newIntegrityCategory, expectedVersion); - } - else if (integrityCategory == IntegrityCategory.AppInstallerNotInstalled || - integrityCategory == IntegrityCategory.AppInstallerNotSupported || - integrityCategory == IntegrityCategory.Failure) - { - // If we are here and expectedVersion is empty, it means that they just ran Repair-WinGetPackageManager. - // When there is not version specified, we don't want to assume an empty version means latest, but in - // this particular case we need to. - if (string.IsNullOrEmpty(expectedVersion)) - { - var gitHubRelease = new GitHubRelease(); - expectedVersion = gitHubRelease.GetLatestVersionTagName(false); - } - - if (this.DownloadAndInstall(expectedVersion, false)) - { - result = Succeeded; - } - else - { - this.WriteDebug($"Failed installing {expectedVersion}"); - } - } - else if (integrityCategory == IntegrityCategory.AppExecutionAliasDisabled) - { - // Sorry, but the user has to manually enabled it. - this.WriteInformation(Resources.AppExecutionAliasDisabledHelpMessage, WriteInformationTags); - } - else - { - this.WriteInformation(Resources.WinGetNotSupportedMessage, WriteInformationTags); - } - - this.WriteObject(result); - } - - private int VerifyWinGetInstall(IntegrityCategory integrityCategory, string expectedVersion) - { - if (integrityCategory == IntegrityCategory.Installed) - { - // Nothing to do - this.WriteDebug($"WinGet is in a good state."); - return Succeeded; - } - else if (integrityCategory == IntegrityCategory.UnexpectedVersion) - { - // The versions are different, download and install. - if (!this.InstallDifferentVersion(new WinGetVersion(expectedVersion))) - { - this.WriteDebug($"Failed installing {expectedVersion}"); - } - else - { - return Succeeded; - } - } - - return Failed; - } - - private bool InstallDifferentVersion(WinGetVersion toInstallVersion) - { - var installedVersion = WinGetVersion.InstalledWinGetVersion; - - this.WriteDebug($"Installed WinGet version {installedVersion.TagVersion}"); - this.WriteDebug($"Installing WinGet version {toInstallVersion.TagVersion}"); - - bool downgrade = false; - if (installedVersion.CompareAsDeployment(toInstallVersion) > 0) - { - downgrade = true; - } - - return this.DownloadAndInstall(toInstallVersion.TagVersion, downgrade); - } - - private bool DownloadAndInstall(string versionTag, bool downgrade) - { - // Download and install. - var gitHubRelease = new GitHubRelease(); - var downloadedMsixBundlePath = gitHubRelease.DownloadRelease(versionTag); - - var appxModule = new AppxModuleHelper(this); - appxModule.AddAppInstallerBundle(downloadedMsixBundlePath, downgrade); - - // Verify that is installed - var integrityCategory = WinGetIntegrity.GetIntegrityCategory(this, versionTag); - if (integrityCategory != IntegrityCategory.Installed) - { - return false; - } - - this.WriteDebug($"Installed WinGet version {versionTag}"); - return true; - } - - private void RepairEnvPath() - { - // Add windows app path to user PATH environment variable - Utilities.AddWindowsAppToPath(); - - // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. - string envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); - string envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); - string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; - this.SessionState.PSVariable.Set(EnvPath, newPwshPathEnv); - - this.WriteDebug($"PATH environment variable updated"); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/SetUserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/SetUserSettingsCommand.cs @@ -1,107 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="SetUserSettingsCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System; - using System.Collections; - using System.IO; - using System.Linq; - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - /// <summary> - /// Sets the specified user settings into the winget user settings. If the merge switch is on, merges current user - /// settings with the input settings. Otherwise, overwrites the input settings. - /// </summary> - [Cmdlet(VerbsCommon.Set, Constants.WinGetNouns.UserSettings)] - [OutputType(typeof(Hashtable))] - public sealed class SetUserSettingsCommand : BaseUserSettingsCommand - { - /// <summary> - /// Gets or sets the input user settings. - /// </summary> - [Parameter( - Mandatory = true, - ValueFromPipelineByPropertyName = true)] - public Hashtable UserSettings { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether to merge the current user settings and the input settings. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Merge { get; set; } - - /// <summary> - /// Process input of cmdlet. - /// </summary> - protected override void ProcessRecord() - { - var newSettings = HashtableToJObject(this.UserSettings); - - // Merge settings. - if (this.Merge.ToBool()) - { - var currentSettings = this.LocalSettingsFileToJObject(); - - // To make the input settings triumph, they need to be merged into the existing settings. - currentSettings.Merge(newSettings, new JsonMergeSettings - { - MergeArrayHandling = MergeArrayHandling.Union, - MergeNullValueHandling = MergeNullValueHandling.Ignore, - }); - - newSettings = currentSettings; - } - - // Add schema if not there. - if (!newSettings.ContainsKey(SchemaKey)) - { - newSettings.Add(SchemaKey, SchemaValue); - } - - var orderedSettings = CreateAlphabeticallyOrderedJObject(newSettings); - - // Write settings. - var settingsJson = orderedSettings.ToString(Formatting.Indented); - File.WriteAllText( - WinGetSettingsFilePath, - settingsJson); - - this.WriteObject(this.ConvertToHashtable(settingsJson)); - } - - /// <summary> - /// Helper method to order alphabetically properties. Newtonsoft doesn't have a nice way - /// to do it via a custom JsonConverter. - /// </summary> - /// <param name="jObject">JObject.</param> - /// <returns>New ordered JObject.</returns> - private static JObject CreateAlphabeticallyOrderedJObject(JObject jObject) - { - JObject newJObject = new (); - var orderedProperties = jObject.Properties().OrderBy(p => p.Name, StringComparer.Ordinal); - foreach (var property in orderedProperties) - { - if (property.Value.Type == JTokenType.Object) - { - newJObject.Add( - property.Name, - CreateAlphabeticallyOrderedJObject((JObject)property.Value)); - } - else - { - newJObject.Add(property); - } - } - - return newJObject; - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/TestUserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/TestUserSettingsCommand.cs @@ -1,137 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="TestUserSettingsCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System; - using System.Collections; - using System.Management.Automation; - using System.Management.Automation.Language; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Newtonsoft.Json.Linq; - - /// <summary> - /// Compare the specified user settings with the winget user settings. - /// </summary> - [Cmdlet(VerbsDiagnostic.Test, Constants.WinGetNouns.UserSettings)] - [OutputType(typeof(bool))] - public sealed class TestUserSettingsCommand : BaseUserSettingsCommand - { - /// <summary> - /// Gets or sets the input user settings. - /// </summary> - [Parameter( - Mandatory = true, - ValueFromPipelineByPropertyName = true)] - public Hashtable UserSettings { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether to ignore comparing settings that are not part of the input. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter IgnoreNotSet { get; set; } - - /// <summary> - /// Process the cmdlet and writes the result of the comparison. - /// </summary> - protected override void ProcessRecord() - { - this.WriteObject(this.CompareUserSettings()); - } - - private bool CompareUserSettings() - { - try - { - var currentSettings = this.LocalSettingsFileToJObject(); - var newSettings = HashtableToJObject(this.UserSettings); - - // Don't fail because of the schema. - if (currentSettings.ContainsKey(SchemaKey)) - { - currentSettings.Remove(SchemaKey); - } - - if (newSettings.ContainsKey(SchemaKey)) - { - newSettings.Remove(SchemaKey); - } - - if (this.IgnoreNotSet.ToBool()) - { - return this.PartialDeepEquals(newSettings, currentSettings); - } - - return JToken.DeepEquals(newSettings, currentSettings); - } - catch (Exception e) - { - this.WriteDebug(e.Message); - return false; - } - } - - /// <summary> - /// Partially compares json. All properties and values of json must exist and have the same value - /// as otherJson. - /// This doesn't support deep JArray object comparison, but we don't have arrays of type object so far :). - /// </summary> - /// <param name="json">Main json.</param> - /// <param name="otherJson">otherJson.</param> - /// <returns>True is otherJson partially contains json.</returns> - private bool PartialDeepEquals(JToken json, JToken otherJson) - { - if (JToken.DeepEquals(json, otherJson)) - { - return true; - } - - // If they are a JValue (string, integer, date, etc) or they are a JArray and DeepEquals fails then not equal. - if ((json is JValue && otherJson is JValue) || - (json is JArray && otherJson is JArray)) - { - this.WriteDebug($"'{json.ToString(Newtonsoft.Json.Formatting.None)}' != " + - $"'{otherJson.ToString(Newtonsoft.Json.Formatting.None)}'"); - return false; - } - - // If its not the same type then don't bother. - if (json.Type != otherJson.Type) - { - this.WriteDebug($"Mismatch types '{json.ToString(Newtonsoft.Json.Formatting.None)}' " + - $"'{otherJson.ToString(Newtonsoft.Json.Formatting.None)}'"); - return false; - } - - // Look deeply. - if (json.Type == JTokenType.Object) - { - var jObject = (JObject)json; - var otherJObject = (JObject)otherJson; - - var properties = jObject.Properties(); - foreach (var property in properties) - { - // If the property is not there then give up. - if (!otherJObject.ContainsKey(property.Name)) - { - this.WriteDebug($"{property.Name} not found."); - return false; - } - - if (!this.PartialDeepEquals(property.Value, otherJObject.GetValue(property.Name))) - { - // Found inequality within a property. We are done. - return false; - } - } - } - - return true; - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/UninstallPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/UninstallPackageCommand.cs @@ -1,104 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="UninstallPackageCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Helpers; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Uninstalls a package from the local system. - /// </summary> - [Cmdlet( - VerbsLifecycle.Uninstall, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [OutputType(typeof(PSObjects.UninstallResult))] - public sealed class UninstallPackageCommand : BasePackageCommand - { - /// <summary> - /// Gets or sets the desired mode for the uninstallation process. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public PackageUninstallMode Mode { get; set; } = PackageUninstallMode.Default; - - /// <summary> - /// Gets or sets a value indicating whether to continue upon non security related failures. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// <summary> - /// Uninstalls a package from the local system. - /// </summary> - protected override void ProcessRecord() - { - this.GetPackageAndExecute(CompositeSearchBehavior.LocalCatalogs, (package, version) => - { - UninstallOptions options = this.GetUninstallOptions(version); - UninstallResult result = this.UninstallPackage(package, options); - this.WriteObject(new PSObjects.UninstallResult(result)); - }); - } - - private UninstallOptions GetUninstallOptions(PackageVersionId version) - { - var options = ComObjectFactory.Value.CreateUninstallOptions(); - options.Force = this.Force.ToBool(); - if (this.Log != null) - { - options.LogOutputPath = this.Log; - } - - options.PackageUninstallMode = this.Mode; - if (version != null) - { - options.PackageVersionId = version; - } - - return options; - } - - private UninstallResult UninstallPackage( - CatalogPackage package, - UninstallOptions options) - { - string activity = string.Format( - Resources.ProgressRecordActivityUninstalling, - package.Name); - - var operation = PackageManager.Value.UninstallPackageAsync(package, options); - WriteProgressAdapter adapter = new (this); - operation.Progress = (context, progress) => - { - adapter.WriteProgress(new ProgressRecord(1, activity, progress.State.ToString()) - { - RecordType = ProgressRecordType.Processing, - }); - }; - operation.Completed = (context, status) => - { - adapter.WriteProgress(new ProgressRecord(1, activity, status.ToString()) - { - RecordType = ProgressRecordType.Completed, - }); - adapter.Completed = true; - }; - Console.CancelKeyPress += (sender, e) => - { - operation.Cancel(); - }; - adapter.Wait(); - return operation.GetResults(); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/UpdatePackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/UpdatePackageCommand.cs @@ -1,66 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="UpdatePackageCommand.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// This commands updates a package from the pipeline or from the local system. - /// </summary> - [Cmdlet( - VerbsData.Update, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [OutputType(typeof(PSObjects.InstallResult))] - public sealed class UpdatePackageCommand : BaseInstallCommand - { - /// <summary> - /// Gets or sets a value indicating whether updating to an unknown version is allowed. - /// </summary> - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludeUnknown { get; set; } - - /// <summary> - /// Updates a package from the pipeline or from the local system. - /// </summary> - protected override void ProcessRecord() - { - base.ProcessRecord(); - this.GetPackageAndExecute(CompositeSearchBehavior.LocalCatalogs, (package, version) => - { - InstallOptions options = this.GetInstallOptions(version); - InstallResult result = this.UpgradePackage(package, options); - this.WriteObject(result); - }); - } - - /// <inheritdoc /> - protected override InstallOptions GetInstallOptions(PackageVersionId version) - { - InstallOptions options = base.GetInstallOptions(version); - options.AllowUpgradeToUnknownVersion = this.IncludeUnknown.ToBool(); - return options; - } - - private InstallResult UpgradePackage( - CatalogPackage package, - InstallOptions options) - { - var operation = PackageManager.Value.UpgradePackageAsync(package, options); - return this.RegisterCallbacksAndWait( - operation, - string.Format( - Resources.ProgressRecordActivityUpdating, - package.Name)); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Constants.cs @@ -1,101 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="Constants.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Common -{ - using Microsoft.Management.Deployment; - - /// <summary> - /// This class contains all of the configurable constants for this project. - /// </summary> - internal static class Constants - { - /// <summary> - /// If a command allows the specification of the maximum number of results to return, this is the lower bound for that value. - /// </summary> - public const uint CountLowerBound = 1; - - /// <summary> - /// If a command allows the specification of the maximum number of results to return, this is the upper bound for that value. - /// </summary> - public const uint CountUpperBound = 1000; - - /// <summary> - /// This parameter set indicates that a package was provided via a parameter or the pipeline and it can be acted on directly. - /// </summary> - public const string GivenSet = "GivenSet"; - - /// <summary> - /// This parameter set indicates that a package was not provided via a parameter or the pipeline and it - /// needs to be found by searching a package source. - /// </summary> - public const string FoundSet = "FoundSet"; - - /// <summary> - /// Parameter set for an specific version parameter. - /// </summary> - public const string IntegrityVersionSet = "IntegrityVersionSet"; - - /// <summary> - /// Parameter set for an latest version with optional prerelease version. - /// </summary> - public const string IntegrityLatestSet = "IntegrityLatestSet"; - - /// <summary> - /// WinGet package family name. - /// </summary> -#if USE_PROD_CLSIDS - public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; -#else - public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; -#endif - - /// <summary> - /// Winget executable name. - /// </summary> -#if USE_PROD_CLSIDS - public const string WinGetExe = "winget.exe"; -#else - public const string WinGetExe = "wingetdev.exe"; -#endif - - /// <summary> - /// Name of PATH environment variable. - /// </summary> - public const string PathEnvVar = "PATH"; - - /// <summary> - /// Nouns used for different cmdlets. Changing this will alter the names of the related commands. - /// </summary> - public static class WinGetNouns - { - /// <summary> - /// WinGet. - /// </summary> - public const string WinGetPackageManager = "WinGetPackageManager"; - - /// <summary> - /// The noun analogue of the <see cref="CatalogPackage" /> class. - /// </summary> - public const string Package = "WinGetPackage"; - - /// <summary> - /// The noun analogue of the <see cref="PackageCatalogReference" /> class. - /// </summary> - public const string Source = "WinGetSource"; - - /// <summary> - /// The noun for any user settings cmdlet. - /// </summary> - public const string UserSettings = "WinGetUserSettings"; - - /// <summary> - /// The noun for winget version. - /// </summary> - public const string Version = "WinGetVersion"; - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/ErrorCode.cs @@ -1,19 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ErrorCode.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Common -{ - /// <summary> - /// Error code constants. - /// </summary> - public static class ErrorCode - { - /// <summary> - /// Error code for ERROR_FILE_NOT_FOUND. - /// </summary> - public const int FileNotFound = unchecked((int)0x80070002); - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/IntegrityCategory.cs @@ -1,64 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="IntegrityCategory.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Common -{ - /// <summary> - /// The type of the integrity check failure. - /// </summary> - public enum IntegrityCategory - { - /// <summary> - /// WinGet is correctly installed. - /// </summary> - Installed, - - /// <summary> - /// The version installed is not what is expected. - /// </summary> - UnexpectedVersion, - - /// <summary> - /// Unknown reason. - /// </summary> - Unknown, - - /// <summary> - /// A failure resulted on a simple winget command that shouldn't happen. - /// </summary> - Failure, - - /// <summary> - /// WindowsAppPath not in PATH environment variable. - /// </summary> - NotInPath, - - /// <summary> - /// Winget's app execution alias disabled. - /// </summary> - AppExecutionAliasDisabled, - - /// <summary> - /// Windows OS is not supported. - /// </summary> - OsNotSupported, - - /// <summary> - /// AppInstaller package is not installed. - /// </summary> - AppInstallerNotInstalled, - - /// <summary> - /// AppInstaller package is not registered. - /// </summary> - AppInstallerNotRegistered, - - /// <summary> - /// Installed App Installer package is not supported. - /// </summary> - AppInstallerNotSupported, - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs @@ -1,105 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="Utilities.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Common -{ - using System; - using System.Security.Principal; - using System.Threading; - - /// <summary> - /// This class contains various helper methods for this project. - /// </summary> - internal static class Utilities - { - /// <summary> - /// Gets a value indicating whether the current assembly is executing in an administrative context. - /// </summary> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ExecutingAsAdministrator - { - get - { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new (identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - } - - /// <summary> - /// Gets a value indicating whether the current assembly is executing as a SYSTEM user. - /// </summary> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ExecutingAsSystem - { - get - { - return WindowsIdentity.GetCurrent().IsSystem; - } - } - - /// <summary> - /// Gets a value indicating whether the current execution context will use in-proc winget. - /// </summary> - public static bool UsesInProcWinget - { - get - { - return ExecutingAsSystem; - } - } - - /// <summary> - /// Gets a value indicating whether the current thread is executing as STA. - /// </summary> - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ThreadIsSTA - { - get - { - return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; - } - } - - /// <summary> - /// Gets the windows app path for local app data. - /// </summary> - public static string LocalDataWindowsAppPath - { - get - { - return Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\WindowsApps"); - } - } - - /// <summary> - /// Gets the windows app path for program files. - /// </summary> - public static string ProgramFilesWindowsAppPath - { - get - { - return Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WindowsApps"); - } - } - - /// <summary> - /// Adds the WindowsApp local app data path to the user environment path. - /// </summary> - 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 @@ -1,165 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WinGetIntegrity.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Common -{ - using System; - using System.ComponentModel; - using System.IO; - using System.Management.Automation; - using Microsoft.WinGet.Client.Exceptions; - using Microsoft.WinGet.Client.Helpers; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Validates winget runs correctly. - /// </summary> - internal static class WinGetIntegrity - { - /// <summary> - /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. - /// </summary> - /// <param name="psCmdlet">The calling cmdlet.</param> - /// <param name="expectedVersion">Expected version.</param> - public static void AssertWinGet(PSCmdlet psCmdlet, string expectedVersion) - { - // In-proc shouldn't have other dependencies and thus should be ok. - if (Utilities.UsesInProcWinget) - { - // Only check the OS version support for in-proc - if (!IsSupportedOSVersion()) - { - throw new WinGetIntegrityException(IntegrityCategory.OsNotSupported); - } - - return; - } - - 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(psCmdlet)); - } - 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); - } - - // WinGet is installed. Verify version if needed. - if (!string.IsNullOrEmpty(expectedVersion)) - { - // 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, - string.Format( - Resources.IntegrityUnexpectedVersionMessage, - installedVersion, - expectedVersion)); - } - } - } - - /// <summary> - /// Verifies winget runs correctly. - /// </summary> - /// <param name="psCmdlet">The calling cmdlet.</param> - /// <param name="expectedVersion">Expected version.</param> - /// <returns>Integrity category.</returns> - public static IntegrityCategory GetIntegrityCategory(PSCmdlet psCmdlet, string expectedVersion) - { - try - { - AssertWinGet(psCmdlet, expectedVersion); - } - catch (WinGetIntegrityException e) - { - return e.Category; - } - - return IntegrityCategory.Installed; - } - - private static IntegrityCategory GetReason(PSCmdlet psCmdlet) - { - // 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... 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)) - { - // 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\ - - // Check OS version - if (!IsSupportedOSVersion()) - { - 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(psCmdlet); - 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; - } - - private static bool IsSupportedOSVersion() - { - // 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; - return osVersion.CompareTo(minWindowsVersion) >= 0; - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Copy-ModuleFiles.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Copy-ModuleFiles.ps1 @@ -1,24 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Copies everything but the binary files to an output directory. - - .PARAMETER OutDir - The output directory where the module manifest will live. -#> - -[CmdletBinding()] -param ( - [Parameter(Mandatory)] - [string] - $OutDir -) - -New-Item $OutDir -ItemType Directory -Force -ErrorAction Stop -Copy-Item "$PSScriptRoot\Microsoft.WinGet.Client.psd1" $OutDir -Force -ErrorAction Stop -Copy-Item "$PSScriptRoot\Microsoft.WinGet.Client.psm1" $OutDir -Force -ErrorAction Stop -Copy-Item "$PSScriptRoot\Format.ps1xml" $OutDir -Force -ErrorAction Stop - -Write-Host 'Done!' -ForegroundColor Green diff --git a/src/PowerShell/Microsoft.WinGet.Client/Copy-PlatformBinaries.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Copy-PlatformBinaries.ps1 @@ -1,48 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Copies built binaries for a specific platform to an output directory. - - .PARAMETER Platform - The platform we are building for. - - .PARAMETER Configuration - The configuration we are building in. - - .PARAMETER OutDir - The base output directory where the module manifest will be. -#> - -[CmdletBinding()] -param ( - [Parameter(Mandatory)] - [string] - $Platform, - - [Parameter(Mandatory)] - [string] - $Configuration, - - [Parameter(Mandatory)] - [string] - $OutDir -) - -# src\x64\Release\Project\net48\Library.Desktop.dll -# src\x64\Release\Project\net6.0-windows10.0.22000.0\Library.Core.dll - -# build\Module\x64\Desktop\Library.Desktop.dll -# build\Module\x64\Core\Library.Core.dll - -$CoreFramework = 'net6.0-windows10.0.22000.0' -$CoreFolderName = 'Core' -$DesktopFramework = 'net48' -$DesktopFolderName = 'Desktop' -$ProjectName = 'Microsoft.WinGet.Client' - -Copy-Item "$PSScriptRoot\..\..\$Platform\$Configuration\$ProjectName\$CoreFramework" "$OutDir\$Platform\$CoreFolderName" -Force -Recurse -ErrorAction Stop -Copy-Item "$PSScriptRoot\..\..\$Platform\$Configuration\$ProjectName\$DesktopFramework" "$OutDir\$Platform\$DesktopFolderName" -Force -Recurse -ErrorAction Stop - -Write-Host 'Done!' -ForegroundColor Green diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/CatalogConnectException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/CatalogConnectException.cs @@ -1,27 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="CatalogConnectException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Failed connecting to catalog. - /// </summary> - [Serializable] - public class CatalogConnectException : RuntimeException - { - /// <summary> - /// Initializes a new instance of the <see cref="CatalogConnectException"/> class. - /// </summary> - public CatalogConnectException() - : base(Resources.CatalogConnectExceptionMessage) - { - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/FindPackagesException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/FindPackagesException.cs @@ -1,36 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="FindPackagesException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Raised when there is an error searching for packages. - /// </summary> - [Serializable] - public class FindPackagesException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="FindPackagesException"/> class. - /// </summary> - /// <param name="status">A <see cref="FindPackagesResultStatus" /> value.</param> - public FindPackagesException(FindPackagesResultStatus status) - : base(string.Format( - Resources.FindPackagesExceptionMessage, - status.ToString())) - { - this.Status = status; - } - - /// <summary> - /// Gets the error status. - /// </summary> - public FindPackagesResultStatus Status { get; private set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/InvalidSourceException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/InvalidSourceException.cs @@ -1,33 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="InvalidSourceException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Invalid source. - /// </summary> - [Serializable] - public class InvalidSourceException : ArgumentException - { - /// <summary> - /// Initializes a new instance of the <see cref="InvalidSourceException"/> class. - /// </summary> - /// <param name="sourceName">Source name.</param> - public InvalidSourceException(string sourceName) - : base(string.Format(Resources.InvalidSourceExceptionMessage, sourceName)) - { - this.SourceName = sourceName; - } - - /// <summary> - /// Gets the source name. - /// </summary> - public string SourceName { get; private set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/InvalidVersionException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/InvalidVersionException.cs @@ -1,33 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="InvalidVersionException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Invalid version. - /// </summary> - [Serializable] - public class InvalidVersionException : ArgumentException - { - /// <summary> - /// Initializes a new instance of the <see cref="InvalidVersionException"/> class. - /// </summary> - /// <param name="version">Version.</param> - public InvalidVersionException(string version) - : base(string.Format(Resources.InvalidVersionExceptionMessage, version)) - { - this.Version = version; - } - - /// <summary> - /// Gets the version. - /// </summary> - public string Version { get; private set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/NoPackageFoundException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/NoPackageFoundException.cs @@ -1,27 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="NoPackageFoundException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// No package found. - /// </summary> - [Serializable] - public class NoPackageFoundException : RuntimeException - { - /// <summary> - /// Initializes a new instance of the <see cref="NoPackageFoundException"/> class. - /// </summary> - public NoPackageFoundException() - : base(Resources.NoPackageFoundExceptionMessage) - { - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/SingleThreadedApartmentException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/SingleThreadedApartmentException.cs @@ -1,27 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="SingleThreadedApartmentException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// No package found. - /// </summary> - [Serializable] - public class SingleThreadedApartmentException : RuntimeException - { - /// <summary> - /// Initializes a new instance of the <see cref="SingleThreadedApartmentException"/> class. - /// </summary> - public SingleThreadedApartmentException() - : base(Resources.SingleThreadedApartmentNotSupportedMessage) - { - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/UserSettingsReadException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/UserSettingsReadException.cs @@ -1,35 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="UserSettingsReadException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Settings.json file is invalid. - /// </summary> - [Serializable] - public class UserSettingsReadException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="UserSettingsReadException"/> class. - /// </summary> - public UserSettingsReadException() - : base(Resources.UserSettingsReadException) - { - } - - /// <summary> - /// Initializes a new instance of the <see cref="UserSettingsReadException"/> class. - /// </summary> - /// <param name="inner">Inner exception.</param> - public UserSettingsReadException(Exception inner) - : base(Resources.UserSettingsReadException, inner) - { - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/VagueCriteriaException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/VagueCriteriaException.cs @@ -1,39 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="VagueCriteriaException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Raised when search criteria for installing or updating a package is too vague. - /// </summary> - [Serializable] - public class VagueCriteriaException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="VagueCriteriaException"/> class. - /// </summary> - /// <param name="results">The list of conflicting packages of length at least two.</param> - public VagueCriteriaException(IReadOnlyList<MatchResult> results) - : base(string.Format( - Resources.VagueCriteriaExceptionMessage, - results[0].CatalogPackage.ToString(null), - results[1].CatalogPackage.ToString(null), - results.Count - 2)) - { - this.MatchResults = results; - } - - /// <summary> - /// Gets the list of conflicting packages. - /// </summary> - public IReadOnlyList<MatchResult> MatchResults { get; private set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLIException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLIException.cs @@ -1,60 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WinGetCLIException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// WinGet cli exception. - /// </summary> - public class WinGetCLIException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="WinGetCLIException"/> class. - /// </summary> - /// <param name="command">Command.</param> - /// <param name="parameters">Parameters.</param> - /// <param name="exitCode">Exit code.</param> - /// <param name="stdOut">Standard output.</param> - /// <param name="stdErr">Standard error.</param> - public WinGetCLIException(string command, string parameters, int exitCode, string stdOut, string stdErr) - : base(string.Format(Resources.WinGetCLIExceptionMessage, command, exitCode)) - { - this.Command = command; - this.Parameters = parameters; - this.ExitCode = exitCode; - this.StdOut = stdOut; - this.StdErr = stdErr; - } - - /// <summary> - /// Gets the command. - /// </summary> - public string Command { get; private set; } - - /// <summary> - /// Gets the parameters. - /// </summary> - public string Parameters { get; private set; } - - /// <summary> - /// Gets the exit code. - /// </summary> - public int ExitCode { get; private set; } - - /// <summary> - /// Gets the standard output. - /// </summary> - public string StdOut { get; private set; } - - /// <summary> - /// Gets the standard error. - /// </summary> - public string StdErr { get; private set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLITimeoutException.cs b/src/PowerShell/Microsoft.WinGet.Client/Exceptions/WinGetCLITimeoutException.cs @@ -1,27 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WinGetCLITimeoutException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// Time out exception for a winget cli command. - /// </summary> - public class WinGetCLITimeoutException : TimeoutException - { - /// <summary> - /// Initializes a new instance of the <see cref="WinGetCLITimeoutException"/> class. - /// </summary> - /// <param name="command">Command.</param> - /// <param name="parameters">Parameters.</param> - 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 @@ -1,67 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WinGetIntegrityException.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Exceptions -{ - using System; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Properties; - - /// <summary> - /// WinGet Integrity exception. - /// </summary> - public class WinGetIntegrityException : Exception - { - /// <summary> - /// Initializes a new instance of the <see cref="WinGetIntegrityException"/> class. - /// </summary> - /// <param name="category">Category failure.</param> - public WinGetIntegrityException(IntegrityCategory category) - : base(GetMessage(category)) - { - this.Category = category; - } - - /// <summary> - /// Initializes a new instance of the <see cref="WinGetIntegrityException"/> class. - /// </summary> - /// <param name="category">Category failure.</param> - /// <param name="inner">Inner exception.</param> - public WinGetIntegrityException(IntegrityCategory category, Exception inner) - : base(GetMessage(category), inner) - { - this.Category = category; - } - - /// <summary> - /// Initializes a new instance of the <see cref="WinGetIntegrityException"/> class. - /// </summary> - /// <param name="category">Category failure.</param> - /// <param name="message">Message.</param> - public WinGetIntegrityException(IntegrityCategory category, string message) - : base(message) - { - this.Category = category; - } - - /// <summary> - /// Gets the category of the integrity failure. - /// </summary> - 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/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/AppxModuleHelper.cs @@ -1,213 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="AppxModuleHelper.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Helpers -{ - using System.Collections.Generic; - 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; - - /// <summary> - /// Helper to make calls to the Appx module. - /// </summary> - 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 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"; - private const string PackageFullName = "PackageFullName"; - - // 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; - - /// <summary> - /// Initializes a new instance of the <see cref="AppxModuleHelper"/> class. - /// </summary> - /// <param name="psCmdlet">The calling cmdlet.</param> - public AppxModuleHelper(PSCmdlet psCmdlet) - { - 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.psCmdlet.InvokeCommand.InvokeScript(GetAppxModule); - if (appxModule is null) - { - this.psCmdlet.InvokeCommand.InvokeScript(ImportModuleCore); - } -#endif - } - - /// <summary> - /// Calls Get-AppxPackage Microsoft.DesktopAppInstaller. - /// </summary> - /// <returns>Result of Get-AppxPackage.</returns> - public PSObject GetAppInstallerObject() - { - return this.GetAppxObject(AppInstallerName); - } - - /// <summary> - /// Gets the string value a property from the Get-AppxPackage object of AppInstaller. - /// </summary> - /// <param name="propertyName">Property name.</param> - /// <returns>Value, null if doesn't exist.</returns> - 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; - } - - /// <summary> - /// Calls Add-AppxPackage with the specified path. - /// </summary> - /// <param name="localPath">The path of the package to add.</param> - /// <param name="downgrade">If the package version is lower than the installed one.</param> - 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(); - - StringBuilder sb = new StringBuilder(); - sb.Append(string.Format(AddAppxPackageFormat, localPath)); - - if (downgrade) - { - sb.Append(ForceUpdateFromAnyVersion); - } - - // 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); - } - - /// <summary> - /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml. - /// </summary> - public void RegisterAppInstaller() - { - string packageFullName = this.GetAppInstallerPropertyValue(PackageFullName); - string appxManifestPath = Path.Combine( - Utilities.ProgramFilesWindowsAppPath, - packageFullName, - AppxManifest); - - this.psCmdlet.InvokeCommand.InvokeScript( - string.Format(AddAppxPackageRegisterFormat, appxManifestPath)); - } - - private PSObject GetAppxObject(string packageName) - { - return this.psCmdlet.InvokeCommand - .InvokeScript(string.Format(GetAppxPackageCommand, packageName)) - .FirstOrDefault(); - } - - private IReadOnlyList<string> GetVCLibsDependencies() - { - var vcLibsDependencies = new List<string>(); - 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()); - } - } - else - { - this.psCmdlet.WriteDebug($"VCLibs are updated."); - } - - return vcLibsDependencies; - } - - private void InstallVCLibsDependencies() - { - var packages = this.GetVCLibsDependencies(); - foreach (var package in packages) - { - this.psCmdlet.WriteDebug($"Installing VCLibs {package}"); - this.psCmdlet.InvokeCommand.InvokeScript(string.Format(AddAppxPackageFormat, package)); - } - } - - 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(Resources.MicrosoftUIXaml27Message); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/CatalogPackageExtensions.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/CatalogPackageExtensions.cs @@ -1,38 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="CatalogPackageExtensions.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Deployment -{ - /// <summary> - /// Extensions for the <see cref="CatalogPackage" /> class. - /// </summary> - public static class CatalogPackageExtensions - { - /// <summary> - /// Converts a <see cref="CatalogPackage" /> to a string previewing the specified version. - /// </summary> - /// <param name="package">A <see cref="CatalogPackage" /> instance.</param> - /// <param name="version">A <see cref="PackageVersionId" /> instance. If null, the latest available version is used.</param> - /// <returns>A <see cref="string" /> instance.</returns> - public static string ToString( - this CatalogPackage package, - PackageVersionId version) - { - if ((version != null) || (package.AvailableVersions.Count > 0)) - { - string versionString = (version is null) - ? package.AvailableVersions[0].Version - : version.Version; - return $"{package.Name} [{package.Id}] Version {versionString}"; - } - else - { - // There were no available versions! - return $"{package.Name} [{package.Id}]"; - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs @@ -1,190 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="ComObjectFactory.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Factories -{ - using System; - using System.IO; - using System.Reflection; - using System.Runtime.InteropServices; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Exceptions; - using Windows.Networking.Sockets; - -#if NET - using WinRT; -#endif - - /// <summary> - /// Constructs instances of classes from the <see cref="Management.Deployment" /> namespace. - /// </summary> - public class ComObjectFactory - { -#if USE_PROD_CLSIDS - private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); - private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); - private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); - private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); - private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); - private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); -#else - private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); - private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); - private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); - private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); - private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); - private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); -#endif - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type FindPackagesOptionsType = Type.GetTypeFromCLSID(FindPackagesOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); - - private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); - private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); - private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); - private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); - private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); - private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); - - /// <summary> - /// Initializes static members of the <see cref="ComObjectFactory"/> class. - /// </summary> - static ComObjectFactory() - { - if (Utilities.UsesInProcWinget) - { - PackageManagerSettings settings = new PackageManagerSettings(); - settings.SetCallerIdentifier("PowerShellInProc"); - } - } - - /// <summary> - /// Creates an instance of the <see cref="PackageManager" /> class. - /// </summary> - /// <returns>A <see cref="PackageManager" /> instance.</returns> - public virtual PackageManager CreatePackageManager() - { - return Create<PackageManager>(PackageManagerType, PackageManagerIid); - } - - /// <summary> - /// Creates an instance of the <see cref="FindPackagesOptions" /> class. - /// </summary> - /// <returns>A <see cref="FindPackagesOptions" /> instance.</returns> - public virtual FindPackagesOptions CreateFindPackagesOptions() - { - return Create<FindPackagesOptions>(FindPackagesOptionsType, FindPackagesOptionsIid); - } - - /// <summary> - /// Creates an instance of the <see cref="CreateCompositePackageCatalogOptions" /> class. - /// </summary> - /// <returns>A <see cref="CreateCompositePackageCatalogOptions" /> instance.</returns> - public virtual CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() - { - return Create<CreateCompositePackageCatalogOptions>(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); - } - - /// <summary> - /// Creates an instance of the <see cref="InstallOptions" /> class. - /// </summary> - /// <returns>An <see cref="InstallOptions" /> instance.</returns> - public virtual InstallOptions CreateInstallOptions() - { - return Create<InstallOptions>(InstallOptionsType, InstallOptionsIid); - } - - /// <summary> - /// Creates an instance of the <see cref="UninstallOptions" /> class. - /// </summary> - /// <returns>A <see cref="UninstallOptions" /> instance.</returns> - public virtual UninstallOptions CreateUninstallOptions() - { - return Create<UninstallOptions>(UninstallOptionsType, UninstallOptionsIid); - } - - /// <summary> - /// Creates an instance of the <see cref="PackageMatchFilter" /> class. - /// </summary> - /// <returns>A <see cref="PackageMatchFilter" /> instance.</returns> - public virtual PackageMatchFilter CreatePackageMatchFilter() - { - return Create<PackageMatchFilter>(PackageMatchFilterType, PackageMatchFilterIid); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static T Create<T>(Type type, in Guid iid) - where T : new() - { - if (Utilities.UsesInProcWinget) - { - string executingAssemblyLocation = Assembly.GetExecutingAssembly().Location; - string executingAssemblyDirectory = Path.GetDirectoryName(executingAssemblyLocation); - SetDllDirectoryW(executingAssemblyDirectory); - - try - { - return new T(); - } - finally - { - SetDllDirectoryW(null); - } - } - - object instance = null; - - if (Utilities.ExecutingAsAdministrator) - { - int hr = WinGetServerManualActivation_CreateInstance(type.GUID, iid, 0, out instance); - - if (hr < 0) - { - if (hr == ErrorCode.FileNotFound) - { - throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); - } - else - { - throw new COMException($"Failed to create instance: {hr}", hr); - } - } - } - else - { - instance = Activator.CreateInstance(type); - } - -#if NET - IntPtr pointer = Marshal.GetIUnknownForObject(instance); - return MarshalInterface<T>.FromAbi(pointer); -#else - return (T)instance; -#endif - } - - [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] - private static extern int WinGetServerManualActivation_CreateInstance( - [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, - [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, - uint flags, - [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SetDllDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string directory); - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/FilterAttribute.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/FilterAttribute.cs @@ -1,25 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="FilterAttribute.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Attributes -{ - using System; - using Microsoft.Management.Deployment; - - /// <summary> - /// A <see cref="FindPackagesOptions" /> is constructed by introspecting on the inheritance tree and - /// looking for parameters that are marked with this attribute. Properties that are marked with this - /// attribute are added to the <see cref="FindPackagesOptions" /> object. - /// </summary> - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class FilterAttribute : Attribute - { - /// <summary> - /// Gets or sets the field that the filter will be matching against. - /// </summary> - public PackageMatchField Field { get; set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/GitHubRelease.cs @@ -1,127 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="GitHubRelease.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -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; - - /// <summary> - /// Handles WinGet's releases in GitHub. - /// </summary> - 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; - - /// <summary> - /// Initializes a new instance of the <see cref="GitHubRelease"/> class. - /// </summary> - public GitHubRelease() - { - this.gitHubClient = new GitHubClient(new ProductHeaderValue(UserAgent)); - } - - /// <summary> - /// Download a release from winget-cli. - /// </summary> - /// <param name="releaseTag">Optional release name. If null, gets latest.</param> - /// <returns>Path where the msix bundle is downloaded.</returns> - public string DownloadRelease(string releaseTag) - { - return this.DownloadReleaseAsync(releaseTag).GetAwaiter().GetResult(); - } - - /// <summary> - /// Gets the latest released version and waits. - /// </summary> - /// <param name="includePreRelease">Include prerelease.</param> - /// <returns>Latest version.</returns> - public string GetLatestVersionTagName(bool includePreRelease) - { - return this.GetLatestVersionAsync(includePreRelease).GetAwaiter().GetResult().TagName; - } - - /// <summary> - /// Downloads a file from a url and waits. - /// </summary> - /// <param name="url">Url.</param> - /// <param name="fileName">File name.</param> - public void DownloadUrl(string url, string fileName) - { - this.DownloadUrlAsync(url, fileName).GetAwaiter().GetResult(); - } - - /// <summary> - /// Download asynchronously a release from winget-cli. - /// </summary> - /// <param name="releaseTag">Optional release name. If null, gets latest.</param> - /// <returns>Path where the msix bundle is downloaded.</returns> - public async Task<string> DownloadReleaseAsync(string 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(); - - var tmpFile = Path.GetTempFileName(); - await this.DownloadUrlAsync(msixBundleAsset.Url, tmpFile); - return tmpFile; - } - - /// <summary> - /// Downloads a file from a url. - /// </summary> - /// <param name="url">Url.</param> - /// <param name="fileName">File name.</param> - /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> - public async Task DownloadUrlAsync(string url, string fileName) - { - var response = await this.gitHubClient.Connection.Get<object>( - new Uri(url), - new Dictionary<string, string>(), - ContentType); - - using var memoryStream = new MemoryStream((byte[])response.Body); - using var fileStream = File.Open(fileName, FileMode.Open); - memoryStream.Position = 0; - await memoryStream.CopyToAsync(fileStream); - } - - /// <summary> - /// Gets the latest released version. - /// </summary> - /// <param name="includePreRelease">Include prerelease.</param> - /// <returns>Latest version.</returns> - internal async Task<Release> 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/WinGetCLICommandResult.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetCLICommandResult.cs @@ -1,75 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WinGetCLICommandResult.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Helpers -{ - using Microsoft.WinGet.Client.Exceptions; - - /// <summary> - /// Winget cli command result. - /// </summary> - internal class WinGetCLICommandResult - { - /// <summary> - /// Initializes a new instance of the <see cref="WinGetCLICommandResult"/> class. - /// </summary> - /// <param name="command">Command.</param> - /// <param name="parameters">Parameters.</param> - /// <param name="exitCode">Exit code.</param> - /// <param name="stdOut">Standard output.</param> - /// <param name="stdErr">Standard error.</param> - public WinGetCLICommandResult(string command, string parameters, int exitCode, string stdOut, string stdErr) - { - this.Command = command; - this.Parameters = parameters; - this.ExitCode = exitCode; - this.StdOut = stdOut; - this.StdErr = stdErr; - } - - /// <summary> - /// Gets the command. - /// </summary> - public string Command { get; private set; } - - /// <summary> - /// Gets the parameters. - /// </summary> - public string Parameters { get; private set; } - - /// <summary> - /// Gets the exit code. - /// </summary> - public int ExitCode { get; private set; } - - /// <summary> - /// Gets the standard output. - /// </summary> - public string StdOut { get; private set; } - - /// <summary> - /// Gets the standard error. - /// </summary> - public string StdErr { get; private set; } - - /// <summary> - /// Verifies exit code. - /// </summary> - /// <param name="exitCode">Optional exit code.</param> - public void VerifyExitCode(int exitCode = 0) - { - if (this.ExitCode != exitCode) - { - throw new WinGetCLIException( - this.Command, - this.Parameters, - this.ExitCode, - this.StdOut, - this.StdErr); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WinGetVersion.cs @@ -1,118 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WinGetVersion.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Helpers -{ - using System; - - /// <summary> - /// WinGetVersion. Parse the string version returned by winget --version to allow comparisons. - /// </summary> - internal class WinGetVersion - { - /// <summary> - /// Initializes a new instance of the <see cref="WinGetVersion"/> class. - /// </summary> - /// <param name="version">String Version.</param> - 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); - } - - /// <summary> - /// Gets the version of the installed winget. - /// </summary> - public static WinGetVersion InstalledWinGetVersion - { - get - { - var wingetCliWrapper = new WingetCLIWrapper(); - var result = wingetCliWrapper.RunCommand("--version"); - return new WinGetVersion(result.StdOut.Replace(Environment.NewLine, string.Empty)); - } - } - - /// <summary> - /// Gets the version as it appears as a tag. - /// </summary> - public string TagVersion { get; } - - /// <summary> - /// Gets the version. - /// </summary> - public System.Version Version { get; } - - /// <summary> - /// Gets a value indicating whether is this version is a prerelease. - /// </summary> - public bool IsPrerelease { get; } - - /// <summary> - /// Version.CompareTo taking into account prerelease. - /// From semver: Pre-release versions have a lower precedence than the associated normal version. - /// </summary> - /// <param name="otherVersion">Other winget version.</param> - /// <returns> - /// 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. - /// </returns> - 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); - } - - /// <summary> - /// Deployment doesn't care about semver or prerelease builds. - /// </summary> - /// <param name="otherVersion">Other version.</param> - /// <returns> - /// 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. - /// </returns> - public int CompareAsDeployment(WinGetVersion otherVersion) - { - return this.Version.CompareTo(otherVersion.Version); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WingetCLIWrapper.cs @@ -1,99 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WingetCLIWrapper.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Helpers -{ - using System.Diagnostics; - using System.IO; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Exceptions; - - /// <summary> - /// Calls winget directly. - /// </summary> - internal class WingetCLIWrapper - { - /// <summary> - /// The file name to use in start info. - /// </summary> - private readonly string wingetPath; - - /// <summary> - /// Initializes a new instance of the <see cref="WingetCLIWrapper"/> 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. - /// </summary> - /// <param name="fullPath">Use full path or not.</param> - public WingetCLIWrapper(bool fullPath = true) - { - if (fullPath) - { - this.wingetPath = WinGetFullPath; - } - else - { - this.wingetPath = Constants.WinGetExe; - } - } - - /// <summary> - /// Gets the full path of winget executable. - /// </summary> - public static string WinGetFullPath - { - get - { - return Path.Combine( - Utilities.LocalDataWindowsAppPath, - Constants.WingetPackageFamilyName, - Constants.WinGetExe); - } - } - - /// <summary> - /// Runs winget command with parameters. - /// </summary> - /// <param name="command">Command.</param> - /// <param name="parameters">Parameters.</param> - /// <param name="timeOut">Time out.</param> - /// <returns>WinGetCommandResult.</returns> - 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 (this.wingetPath, args) - { - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - }, - }; - - p.Start(); - - if (p.WaitForExit(timeOut)) - { - return new WinGetCLICommandResult( - command, - parameters, - p.ExitCode, - p.StandardOutput.ReadToEnd(), - p.StandardError.ReadToEnd()); - } - - throw new WinGetCLITimeoutException(command, parameters); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/WriteProgressAdapter.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/WriteProgressAdapter.cs @@ -1,90 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="WriteProgressAdapter.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Helpers -{ - using System.Collections.Generic; - using System.Management.Automation; - using System.Threading; - - /// <summary> - /// Marshals calls to <see cref="Cmdlet.WriteProgress(ProgressRecord)" /> back to the main thread. - /// </summary> - public class WriteProgressAdapter - { - private readonly AutoResetEvent resetEvent = new (false); - private readonly Queue<ProgressRecord> records = new (); - private readonly Cmdlet cmdlet; - private volatile bool completed = false; - - /// <summary> - /// Initializes a new instance of the <see cref="WriteProgressAdapter" /> class. - /// </summary> - /// <param name="cmdlet">A <see cref="Cmdlet" /> instance.</param> - public WriteProgressAdapter(Cmdlet cmdlet) - { - this.cmdlet = cmdlet; - } - - /// <summary> - /// Sets a value indicating whether the asynchronous operation is finished and the main thread can continue. - /// </summary> - public bool Completed - { - set - { - this.completed = value; - if (value) - { - this.resetEvent.Set(); - } - } - } - - /// <summary> - /// This should be called on the main thread to wait for the asynchronous operation to complete. - /// </summary> - public void Wait() - { - while (!this.completed) - { - lock (this.records) - { - this.Flush(); - } - - this.resetEvent.WaitOne(); - } - - this.Flush(); - } - - /// <summary> - /// This is an analogue of the <see cref="Cmdlet.WriteProgress(ProgressRecord)" /> function. - /// </summary> - /// <param name="record">A <see cref="ProgressRecord" /> instance.</param> - public void WriteProgress(ProgressRecord record) - { - if (record != null) - { - lock (this.records) - { - this.records.Enqueue(record); - } - - this.resetEvent.Set(); - } - } - - private void Flush() - { - while (this.records.Count > 0) - { - this.cmdlet.WriteProgress(this.records.Dequeue()); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj b/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj @@ -1,181 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <!-- If these target frameworks are updated, make sure to also update the .psd1 and .nuspec files.--> - <TargetWindowsVersion>10.0.22000.0</TargetWindowsVersion> - <CoreFramework>net6.0-windows$(TargetWindowsVersion)</CoreFramework> - <DesktopFramework>net48</DesktopFramework> - </PropertyGroup> - - <PropertyGroup> - <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> - <LangVersion>10</LangVersion> - <BuildOutputDirectory>$(SolutionDir)$(Platform)\$(Configuration)\</BuildOutputDirectory> - <OutputPath>$(BuildOutputDirectory)$(MSBuildProjectName)</OutputPath> - <PowerShellModuleOutputDirectory>$(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Client</PowerShellModuleOutputDirectory> - <PowerShellModuleLibDirectory>$(PowerShellModuleOutputDirectory)\lib</PowerShellModuleLibDirectory> - <Platforms>x64;x86;ARM64</Platforms> - <TargetFrameworks>$(CoreFramework);$(DesktopFramework)</TargetFrameworks> - <DocumentationFile>$(OutputPath)\Microsoft.WinGet.Client.xml</DocumentationFile> - </PropertyGroup> - - <PropertyGroup Condition="'$(UseProdCLSIDs)' == 'true'"> - <DefineConstants>USE_PROD_CLSIDS</DefineConstants> - </PropertyGroup> - - <ItemGroup> - <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="Octokit" Version="4.0.3" /> - <PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="all" /> - <PackageReference Include="StyleCop.Analyzers" Version="1.1.118"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> - </PackageReference> - <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" /> - <PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\..\Microsoft.Management.Deployment\Microsoft.Management.Deployment.vcxproj"> - <OutputItemType>Content</OutputItemType> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </ProjectReference> - <ProjectReference Include="..\..\Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj"> - <OutputItemType>Content</OutputItemType> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </ProjectReference> - <ProjectReference Include="..\..\Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.vcxproj"> - <OutputItemType>Content</OutputItemType> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </ProjectReference> - <ProjectReference Include="..\..\WindowsPackageManager\WindowsPackageManager.vcxproj"> - <OutputItemType>Content</OutputItemType> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </ProjectReference> - </ItemGroup> - - <PropertyGroup> - <!-- Disable warning for CS1591 because auto generated Microsoft.Management.Deployment --> - <!-- For our code, we still have stylecops acting on it. --> - <NoWarn>1591</NoWarn> - </PropertyGroup> - - <PropertyGroup Condition="'$(Configuration)'=='Release'"> - <TreatWarningsAsErrors>true</TreatWarningsAsErrors> - </PropertyGroup> - - <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <RuntimeIdentifiers>win-x64;win-x86;win-arm;win-arm64</RuntimeIdentifiers> - </PropertyGroup> - - <PropertyGroup Condition=" '$(Platform)' == 'x64' AND '$(TargetFramework)' == '$(CoreFramework)'"> - <RuntimeIdentifier>win-x64</RuntimeIdentifier> - </PropertyGroup> - - <PropertyGroup Condition=" '$(Platform)' == 'x86' "> - <RuntimeIdentifier>win-x86</RuntimeIdentifier> - </PropertyGroup> - - <PropertyGroup Condition=" '$(Platform)' == 'arm' "> - <RuntimeIdentifier>win-arm</RuntimeIdentifier> - </PropertyGroup> - - <PropertyGroup Condition=" '$(Platform)' == 'arm64' "> - <RuntimeIdentifier>win-arm64</RuntimeIdentifier> - </PropertyGroup> - - <ItemGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.5" /> - <PackageReference Include="Microsoft.PowerShell.SDK" Version="7.2.8" /> - </ItemGroup> - - <ItemGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> - <PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.22000.196" PrivateAssets="all" /> - <PackageReference Include="Microsoft.PowerShell.5.1.ReferenceAssemblies" Version="1.0.0" /> - </ItemGroup> - - <PropertyGroup Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> - <DefineConstants>POWERSHELL_WINDOWS</DefineConstants> - </PropertyGroup> - - <ItemGroup> - <Compile Update="Properties\Resources.Designer.cs"> - <DesignTime>True</DesignTime> - <AutoGen>True</AutoGen> - <DependentUpon>Resources.resx</DependentUpon> - </Compile> - </ItemGroup> - - <ItemGroup> - <EmbeddedResource Update="Properties\Resources.resx"> - <Generator>ResXFileCodeGenerator</Generator> - <LastGenOutput>Resources.Designer.cs</LastGenOutput> - </EmbeddedResource> - </ItemGroup> - - <PropertyGroup Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <CsWinRTIncludes>Microsoft.Management.Deployment</CsWinRTIncludes> - <CsWinRTWindowsMetadata>$(TargetWindowsVersion)</CsWinRTWindowsMetadata> - <!-- Ensure Support for Windows 10, Version 1809 --> - <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> - </PropertyGroup> - - <!-- Copy WinRTAct.dll to PowerShell module directory. Relative path to $(OutputPath) used to support AppInstaller build. --> - <Target Name="CopyWinRTAct" AfterTargets="AfterBuild" Condition="'$(Platform)' != 'ARM' AND '$(TargetFramework)' == '$(CoreFramework)'"> - <PropertyGroup> - <WinRTActDir>$(OutputPath)..\..\..\UndockedRegFreeWinRT\</WinRTActDir> - <!-- The output path for this project is different on some builds where the RIDs are not used in the output. --> - <WinRTActDir Condition="!Exists('$(WinRTActDir)winrtact.dll')">$(OutputPath)..\..\UndockedRegFreeWinRT\</WinRTActDir> - </PropertyGroup> - <ItemGroup> - <WinRTActDll Include="$(WinRTActDir)winrtact.dll" /> - </ItemGroup> - <Message Importance="high" Text="Copying '@(WinRTActDll)'" /> - <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(OutputPath)" /> - <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(PowerShellModuleLibDirectory)\$(CoreFramework)\$(Platform)" /> - <Copy SourceFiles="@(WinRTActDll)" DestinationFolder="$(PowerShellModuleLibDirectory)\$(DesktopFramework)\$(Platform)" /> - </Target> - - <!-- Build Microsoft.Winget.Client PowerShell Module --> - <Target Name="CopyModuleFiles" AfterTargets="AfterBuild"> - <ItemGroup> - <ModuleFiles Include="$(ProjectDir)\Module\**\*.*" /> - </ItemGroup> - <Message Importance="high" Text="Copying PowerShell module files to '$(PowerShellModuleOutputDirectory)'" /> - <Copy SourceFiles="@(ModuleFiles)" DestinationFiles="$(PowerShellModuleOutputDirectory)\%(RecursiveDir)%(Filename)%(Extension)" /> - </Target> - - <Target Name="CopyCoreBinaries" AfterTargets="AfterBuild" Condition="'$(TargetFramework)' == '$(CoreFramework)'"> - <ItemGroup> - <!-- Non-Windows runtime binaries need to be explicitly excluded when building the PowerShell module.--> - <CoreBinaries Include="$(OutputPath)\**\*.*" Exclude="$(OutputPath)\runtimes\**" /> - <WindowsRuntime Include="$(OutputPath)\runtimes\win10*\**\*.*" /> - </ItemGroup> - <Message Importance="high" Text="Copying @(CoreBinaries) to '$(PowerShellModuleLibDirectory)\$(CoreFramework)\$(Platform)'" /> - <Copy SourceFiles="@(CoreBinaries)" DestinationFolder="$(PowerShellModuleLibDirectory)\$(CoreFramework)\$(Platform)\%(RecursiveDir)" /> - <Copy SourceFiles="@(WindowsRuntime)" DestinationFolder="$(PowerShellModuleLibDirectory)\$(CoreFramework)\$(Platform)\runtimes\%(RecursiveDir)" /> - </Target> - - <Target Name="CopyDesktopBinaries" AfterTargets="AfterBuild" Condition="'$(TargetFramework)' == '$(DesktopFramework)'"> - <ItemGroup> - <DesktopBinaries Include="$(OutputPath)\**\*.*" /> - </ItemGroup> - <Message Importance="high" Text="Copying @(DesktopBinaries) to '$(PowerShellModuleLibDirectory)\$(DesktopFramework)\$(Platform)\Desktop'" /> - <Copy SourceFiles="@(DesktopBinaries)" DestinationFolder="$(PowerShellModuleLibDirectory)\$(DesktopFramework)\$(Platform)\%(RecursiveDir)" /> - </Target> - - <!-- Rename Microsoft.Management.Deployment.InProc.dll to Microsoft.Management.Deployment.dll --> - <Target Name="RenameMicrosoftManagementDeployment" AfterTargets="AfterBuild"> - <ItemGroup> - <RenameModuleFiles Include="$(PowerShellModuleLibDirectory)\**\Microsoft.Management.Deployment.InProc.dll"> - <NewName>Microsoft.Management.Deployment.dll</NewName> - </RenameModuleFiles> - </ItemGroup> - <Message Importance="high" Text="Renaming file '%(RenameModuleFiles.Identity)' to '$(PowerShellModuleLibDirectory)\%(RenameModuleFiles.RecursiveDir)\%(RenameModuleFiles.NewName)'" /> - <Move SourceFiles="%(RenameModuleFiles.Identity)" DestinationFiles="$(PowerShellModuleLibDirectory)\%(RenameModuleFiles.RecursiveDir)\%(RenameModuleFiles.NewName)" /> - </Target> - -</Project> diff --git a/src/PowerShell/Microsoft.WinGet.Client/Module/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/Module/Format.ps1xml @@ -1,269 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<Configuration> - <ViewDefinitions> - <View> - <Name>Microsoft.WinGet.Client.PSObjects.FoundCatalogPackage</Name> - <ViewSelectedBy> - <TypeName>Microsoft.WinGet.Client.PSObjects.FoundCatalogPackage</TypeName> - </ViewSelectedBy> - <TableControl> - <TableHeaders> - <TableColumnHeader> - <Label>Name</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Id</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Version</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Source</Label> - </TableColumnHeader> - </TableHeaders> - <TableRowEntries> - <TableRowEntry> - <TableColumnItems> - <TableColumnItem> - <ScriptBlock>$_.Name</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Id</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Version</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Source</ScriptBlock> - </TableColumnItem> - </TableColumnItems> - </TableRowEntry> - </TableRowEntries> - </TableControl> - </View> - <View> - <Name>Microsoft.WinGet.Client.PSObjects.InstalledCatalogPackage</Name> - <ViewSelectedBy> - <TypeName>Microsoft.WinGet.Client.PSObjects.InstalledCatalogPackage</TypeName> - </ViewSelectedBy> - <TableControl> - <TableHeaders> - <TableColumnHeader> - <Label>Name</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Id</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Version</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Available</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Source</Label> - </TableColumnHeader> - </TableHeaders> - <TableRowEntries> - <TableRowEntry> - <TableColumnItems> - <TableColumnItem> - <ScriptBlock>$_.Name</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Id</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Version</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>if ($_.IsUpdateAvailable) { $_.AvailableVersions[0] }</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Source</ScriptBlock> - </TableColumnItem> - </TableColumnItems> - </TableRowEntry> - </TableRowEntries> - </TableControl> - </View> - <View> - <Name>Microsoft.WinGet.Client.PSObjects.InstallResult</Name> - <ViewSelectedBy> - <TypeName>Microsoft.WinGet.Client.PSObjects.InstallResult</TypeName> - </ViewSelectedBy> - <TableControl> - <TableHeaders> - <TableColumnHeader> - <Label>InstallerErrorCode</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Status</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>RebootRequired</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>ExtendedErrorCode</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>CorrelationData</Label> - </TableColumnHeader> - </TableHeaders> - <TableRowEntries> - <TableRowEntry> - <TableColumnItems> - <TableColumnItem> - <ScriptBlock>$_.InstallerErrorCode</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Status</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.RebootRequired</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.ExtendedErrorCode</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.CorrelationData</ScriptBlock> - </TableColumnItem> - </TableColumnItems> - </TableRowEntry> - </TableRowEntries> - </TableControl> - </View> - <View> - <Name>Microsoft.WinGet.Client.PSObjects.UninstallResult</Name> - <ViewSelectedBy> - <TypeName>Microsoft.WinGet.Client.PSObjects.UninstallResult</TypeName> - </ViewSelectedBy> - <TableControl> - <TableHeaders> - <TableColumnHeader> - <Label>UninstallerErrorCode</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Status</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>RebootRequired</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>ExtendedErrorCode</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>CorrelationData</Label> - </TableColumnHeader> - </TableHeaders> - <TableRowEntries> - <TableRowEntry> - <TableColumnItems> - <TableColumnItem> - <ScriptBlock>$_.UninstallerErrorCode</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Status</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.RebootRequired</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.ExtendedErrorCode</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.CorrelationData</ScriptBlock> - </TableColumnItem> - </TableColumnItems> - </TableRowEntry> - </TableRowEntries> - </TableControl> - </View> - <View> - <Name>Microsoft.WinGet.Client.PSObjects.SourceResult</Name> - <ViewSelectedBy> - <TypeName>Microsoft.WinGet.Client.PSObjects.SourceResult</TypeName> - </ViewSelectedBy> - <TableControl> - <TableHeaders> - <TableColumnHeader> - <Label>Name</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Argument</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Type</Label> - </TableColumnHeader> - </TableHeaders> - <TableRowEntries> - <TableRowEntry> - <TableColumnItems> - <TableColumnItem> - <ScriptBlock>$_.Name</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Argument</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Type</ScriptBlock> - </TableColumnItem> - </TableColumnItems> - </TableRowEntry> - </TableRowEntries> - </TableControl> - </View> - <View> - <Name>Microsoft.WinGet.Client.PSObjects.PackageVersionInfo</Name> - <ViewSelectedBy> - <TypeName>Microsoft.WinGet.Client.PSObjects.PackageVersionInfo</TypeName> - </ViewSelectedBy> - <TableControl> - <TableHeaders> - <TableColumnHeader> - <Label>DisplayName</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Id</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Publisher</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>Channel</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>PackageFamilyNames</Label> - </TableColumnHeader> - <TableColumnHeader> - <Label>ProductCodes</Label> - </TableColumnHeader> - </TableHeaders> - <TableRowEntries> - <TableRowEntry> - <TableColumnItems> - <TableColumnItem> - <ScriptBlock>$_.DisplayName</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Id</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Publisher</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.Channel</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.PackageFamilyNames</ScriptBlock> - </TableColumnItem> - <TableColumnItem> - <ScriptBlock>$_.ProductCodes</ScriptBlock> - </TableColumnItem> - </TableColumnItems> - </TableRowEntry> - </TableRowEntries> - </TableControl> - </View> - </ViewDefinitions> -</Configuration>- \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psd1 @@ -1,186 +0,0 @@ -# -# Module manifest for module 'Microsoft.WinGet.Client' -# -# Generated by: Microsoft Corporation -# -# Generated on: 7/7/2022 -# - -@{ - -# Script module or binary module file associated with this manifest. -RootModule = 'Microsoft.WinGet.Client.psm1' - -# Version number of this module. -ModuleVersion = '0.1.0' - -# Supported PSEditions -CompatiblePSEditions = 'Core' - -# ID used to uniquely identify this module -GUID = 'e11157e2-cd24-4250-83b8-c6654ea4926a' - -# Author of this module -Author = 'Microsoft Corporation' - -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' - -# Copyright statement for this module -Copyright = '(c) Microsoft Corporation. All rights reserved.' - -# Description of the functionality provided by this module -Description = 'PowerShell Module for the Windows Package Manager Client.' - -# Minimum version of the PowerShell engine required by this module -PowerShellVersion = '5.1.0' - -# Name of the PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -FormatsToProcess = 'Format.ps1xml' - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -NestedModules = if ($env:PROCESSOR_ARCHITECTURE -like 'x86') -{ - if ($PSEdition -eq 'Core') - { - "lib\net6.0-windows10.0.22000.0\x86\Microsoft.WinGet.Client.dll" - } - else - { - "lib\net48\x86\Microsoft.WinGet.Client.dll" - } -} -else -{ - if ($PSEdition -eq 'Core') - { - "lib\net6.0-windows10.0.22000.0\x64\Microsoft.WinGet.Client.dll" - } - else - { - "lib\net48\x64\Microsoft.WinGet.Client.dll" - } -} - -# 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 = @( - 'Enable-WinGetSetting', - 'Disable-WinGetSetting', - 'Add-WinGetSource', - 'Remove-WinGetSource', - 'Reset-WinGetSource', - 'Get-WinGetSettings' -) - -# 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', - 'Install-WinGetPackage', - 'Uninstall-WinGetPackage', - 'Update-WinGetPackage', - 'Get-WinGetUserSettings', - 'Set-WinGetUserSettings', - 'Test-WinGetUserSettings', - 'Assert-WinGetPackageManager', - 'Repair-WinGetPackageManager' -) - -# Variables to export from this module -# VariablesToExport = @() - -# Aliases 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 aliases to export. -AliasesToExport = @() - -# DSC resources to export from this module -# DscResourcesToExport = @() - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @( - 'CrescendoBuilt', - 'PSEdition_Desktop', - 'PSEdition_Core', - 'Windows', - 'WindowsPackageManager', - 'WinGet' - ) - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - ProjectUri = 'https://github.com/microsoft/winget-cli' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - # Prerelease string of this module - Prerelease = 'alpha' - - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false - - # External dependent modules of this module - # ExternalModuleDependencies = @() - - } # End of PSData hashtable - - - # CrescendoVersion - CrescendoVersion = '1.0.0' - - # CrescendoGenerated - CrescendoGenerated = '07/07/2022 00:00:00' - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' - -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml @@ -0,0 +1,269 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<Configuration> + <ViewDefinitions> + <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSFoundCatalogPackage</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSFoundCatalogPackage</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>Name</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Id</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Version</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Source</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.Name</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Id</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Version</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Source</ScriptBlock> + </TableColumnItem> + </TableColumnItems> + </TableRowEntry> + </TableRowEntries> + </TableControl> + </View> + <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSInstalledCatalogPackage</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSInstalledCatalogPackage</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>Name</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Id</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Version</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Available</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Source</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.Name</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Id</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Version</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>if ($_.IsUpdateAvailable) { $_.AvailableVersions[0] }</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Source</ScriptBlock> + </TableColumnItem> + </TableColumnItems> + </TableRowEntry> + </TableRowEntries> + </TableControl> + </View> + <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSInstallResult</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSInstallResult</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>InstallerErrorCode</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Status</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>RebootRequired</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>ExtendedErrorCode</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>CorrelationData</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.InstallerErrorCode</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Status</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.RebootRequired</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.ExtendedErrorCode</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.CorrelationData</ScriptBlock> + </TableColumnItem> + </TableColumnItems> + </TableRowEntry> + </TableRowEntries> + </TableControl> + </View> + <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSUninstallResult</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSUninstallResult</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>UninstallerErrorCode</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Status</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>RebootRequired</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>ExtendedErrorCode</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>CorrelationData</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.UninstallerErrorCode</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Status</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.RebootRequired</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.ExtendedErrorCode</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.CorrelationData</ScriptBlock> + </TableColumnItem> + </TableColumnItems> + </TableRowEntry> + </TableRowEntries> + </TableControl> + </View> + <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSSourceResult</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSSourceResult</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>Name</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Argument</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Type</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.Name</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Argument</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Type</ScriptBlock> + </TableColumnItem> + </TableColumnItems> + </TableRowEntry> + </TableRowEntries> + </TableControl> + </View> + <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSPackageVersionInfo</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSPackageVersionInfo</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>DisplayName</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Id</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Publisher</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Channel</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>PackageFamilyNames</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>ProductCodes</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.DisplayName</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Id</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Publisher</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Channel</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.PackageFamilyNames</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.ProductCodes</ScriptBlock> + </TableColumnItem> + </TableColumnItems> + </TableRowEntry> + </TableRowEntries> + </TableControl> + </View> + </ViewDefinitions> +</Configuration>+ \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 @@ -0,0 +1,186 @@ +# +# Module manifest for module 'Microsoft.WinGet.Client' +# +# Generated by: Microsoft Corporation +# +# Generated on: 7/7/2022 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'Microsoft.WinGet.Client.psm1' + +# Version number of this module. +ModuleVersion = '0.1.0' + +# Supported PSEditions +CompatiblePSEditions = 'Core' + +# ID used to uniquely identify this module +GUID = 'e11157e2-cd24-4250-83b8-c6654ea4926a' + +# Author of this module +Author = 'Microsoft Corporation' + +# Company or vendor of this module +CompanyName = 'Microsoft Corporation' + +# Copyright statement for this module +Copyright = '(c) Microsoft Corporation. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'PowerShell Module for the Windows Package Manager Client.' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.1.0' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +FormatsToProcess = 'Format.ps1xml' + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +NestedModules = if ($env:PROCESSOR_ARCHITECTURE -like 'x86') +{ + if ($PSEdition -eq 'Core') + { + "runtimes\win10-x86\lib\net6.0-windows10.0.22000.0\Microsoft.WinGet.Client.Cmdlets.dll" + } + else + { + "runtimes\win10-x86\lib\net48\Microsoft.WinGet.Client.Cmdlets.dll" + } +} +else +{ + if ($PSEdition -eq 'Core') + { + "runtimes\win10-x64\lib\net6.0-windows10.0.22000.0\Microsoft.WinGet.Client.Cmdlets.dll" + } + else + { + "runtimes\win10-x64\lib\net48\Microsoft.WinGet.Client.Cmdlets.dll" + } +} + +# 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 = @( + 'Enable-WinGetSetting', + 'Disable-WinGetSetting', + 'Add-WinGetSource', + 'Remove-WinGetSource', + 'Reset-WinGetSource', + 'Get-WinGetSettings' +) + +# 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', + 'Install-WinGetPackage', + 'Uninstall-WinGetPackage', + 'Update-WinGetPackage', + 'Get-WinGetUserSettings', + 'Set-WinGetUserSettings', + 'Test-WinGetUserSettings', + 'Assert-WinGetPackageManager', + 'Repair-WinGetPackageManager' +) + +# Variables to export from this module +# VariablesToExport = @() + +# Aliases 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 aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @( + 'CrescendoBuilt', + 'PSEdition_Desktop', + 'PSEdition_Core', + 'Windows', + 'WindowsPackageManager', + 'WinGet' + ) + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/microsoft/winget-cli' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = 'alpha' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + + # CrescendoVersion + CrescendoVersion = '1.0.0' + + # CrescendoGenerated + CrescendoGenerated = '07/07/2022 00:00:00' + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Module/Microsoft.WinGet.Client.psm1 b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psm1 diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/CatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/CatalogPackage.cs @@ -1,132 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="CatalogPackage.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - using System.Linq; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Exceptions; - - /// <summary> - /// CatalogPackage wrapper object for displaying to PowerShell. - /// </summary> - public abstract class CatalogPackage - { - /// <summary> - /// Initializes a new instance of the <see cref="CatalogPackage"/> class. - /// </summary> - /// <param name="catalogPackage">CatalogPackage COM object.</param> - public CatalogPackage(Management.Deployment.CatalogPackage catalogPackage) - { - this.CatalogPackageCOM = catalogPackage; - } - - /// <summary> - /// Gets the name of the catalog package. - /// </summary> - public string Name - { - get - { - return this.CatalogPackageCOM.Name; - } - } - - /// <summary> - /// Gets the id of the catalog package. - /// </summary> - public string Id - { - get - { - return this.CatalogPackageCOM.Id; - } - } - - /// <summary> - /// Gets a value indicating whether an update is available. - /// </summary> - public bool IsUpdateAvailable - { - get - { - return this.CatalogPackageCOM.IsUpdateAvailable; - } - } - - /// <summary> - /// Gets the source name of the catalog package. - /// </summary> - public string Source - { - get - { - return this.CatalogPackageCOM.DefaultInstallVersion.PackageCatalog.Info.Name; - } - } - - /// <summary> - /// Gets list of strings representing the available versions. - /// </summary> - public string[] AvailableVersions - { - get - { - return this.AvailablePackageVersionIds.Select(i => i.Version).ToArray(); - } - } - - /// <summary> - /// Gets the version of the catalog package. - /// </summary> - public abstract string Version { get; } - - /// <summary> - /// Gets the catalog package COM object. - /// </summary> - protected Management.Deployment.CatalogPackage CatalogPackageCOM { get; } - - /// <summary> - /// Gets a list of available package version ids for the package. - /// </summary> - private PackageVersionId[] AvailablePackageVersionIds - { - get - { - return this.CatalogPackageCOM.AvailableVersions.ToArray(); - } - } - - /// <summary> - /// Checks the installed status of the catalog package. - /// </summary> - /// <returns>CheckInstalledStatus string.</returns> - public string CheckInstalledStatus() - { - return this.CatalogPackageCOM.CheckInstalledStatus().Status.ToString(); - } - - /// <summary> - /// Gets the PackageVersionInfo PSObject that corresponds with the version string. - /// </summary> - /// <param name="version">Version string.</param> - /// <returns>PackageVersionInfo PSObject.</returns> - /// <exception cref="NoPackageFoundException">Throws an exception if no package is found.</exception> - public PackageVersionInfo GetPackageVersionInfo(string version) - { - // get specific version that matches - PackageVersionId packageVersionId = this.AvailablePackageVersionIds.FirstOrDefault(x => x.Version == version); - if (packageVersionId != null) - { - return new PackageVersionInfo(this.CatalogPackageCOM.GetPackageVersionInfo(packageVersionId)); - } - else - { - throw new NoPackageFoundException(); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/FoundCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/FoundCatalogPackage.cs @@ -1,29 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="FoundCatalogPackage.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// <summary> - /// FoundCatalogPackage wrapper object for displaying to PowerShell. - /// </summary> - public class FoundCatalogPackage : CatalogPackage - { - /// <summary> - /// Initializes a new instance of the <see cref="FoundCatalogPackage"/> class. - /// </summary> - /// <param name="catalogPackage">The catalog package COM object.</param> - public FoundCatalogPackage(Management.Deployment.CatalogPackage catalogPackage) - : base(catalogPackage) - { - } - - /// <inheritdoc/> - public override string Version - { - get { return this.CatalogPackageCOM.DefaultInstallVersion.Version; } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/InstallResult.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/InstallResult.cs @@ -1,82 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="InstallResult.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - using System; - - /// <summary> - /// InstallResult wrapper object for displaying to PowerShell. - /// </summary> - public class InstallResult - { - private Management.Deployment.InstallResult installResult; - - /// <summary> - /// Initializes a new instance of the <see cref="InstallResult"/> class. - /// </summary> - /// <param name="installResult">The install result COM object.</param> - public InstallResult(Management.Deployment.InstallResult installResult) - { - this.installResult = installResult; - } - - /// <summary> - /// Gets the correlation data of the install result. - /// </summary> - public string CorrelationData - { - get - { - return this.installResult.CorrelationData; - } - } - - /// <summary> - /// Gets the error code of an install. - /// </summary> - public uint InstallerErrorCode - { - get - { - return this.installResult.InstallerErrorCode; - } - } - - /// <summary> - /// Gets the extended error code exception of the failed install result. - /// </summary> - public Exception ExtendedErrorCode - { - get - { - return this.installResult.ExtendedErrorCode; - } - } - - /// <summary> - /// Gets a value indicating whether a reboot is required. - /// </summary> - public bool RebootRequired - { - get - { - return this.installResult.RebootRequired; - } - } - - /// <summary> - /// Gets the status of the install. - /// </summary> - public string Status - { - get - { - return this.installResult.Status.ToString(); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/InstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/InstalledCatalogPackage.cs @@ -1,29 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="InstalledCatalogPackage.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// <summary> - /// CatalogPackage wrapper object for displaying to PowerShell. - /// </summary> - public class InstalledCatalogPackage : CatalogPackage - { - /// <summary> - /// Initializes a new instance of the <see cref="InstalledCatalogPackage"/> class. - /// </summary> - /// <param name="catalogPackage">The catalog package COM object.</param> - public InstalledCatalogPackage(Management.Deployment.CatalogPackage catalogPackage) - : base(catalogPackage) - { - } - - /// <inheritdoc/> - public override string Version - { - get { return this.CatalogPackageCOM.InstalledVersion.Version; } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/PackageVersionInfo.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/PackageVersionInfo.cs @@ -1,103 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="PackageVersionInfo.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - using System.Linq; - - /// <summary> - /// CatalogPackage wrapper object for displaying to PowerShell. - /// </summary> - public class PackageVersionInfo - { - private Management.Deployment.PackageVersionInfo packageVersionInfo; - - /// <summary> - /// Initializes a new instance of the <see cref="PackageVersionInfo"/> class. - /// </summary> - /// <param name="packageVersionInfo">PackageVersionInfo COM object.</param> - public PackageVersionInfo(Management.Deployment.PackageVersionInfo packageVersionInfo) - { - this.packageVersionInfo = packageVersionInfo; - } - - /// <summary> - /// Gets the name of the package version info. - /// </summary> - public string DisplayName - { - get - { - return this.packageVersionInfo.DisplayName; - } - } - - /// <summary> - /// Gets the id of the package version info. - /// </summary> - public string Id - { - get - { - return this.packageVersionInfo.Id; - } - } - - /// <summary> - /// Gets the publisher of the package version info. - /// </summary> - public string Publisher - { - get - { - return this.packageVersionInfo.Publisher; - } - } - - /// <summary> - /// Gets the channel of the package version info. - /// </summary> - public string Channel - { - get - { - return this.packageVersionInfo.Channel; - } - } - - /// <summary> - /// Gets the list of package family names of the package version info. - /// </summary> - public string[] PackageFamilyNames - { - get - { - return this.packageVersionInfo.PackageFamilyNames.ToArray(); - } - } - - /// <summary> - /// Gets the list of product codes of the package version info. - /// </summary> - public string[] ProductCodes - { - get - { - return this.packageVersionInfo.ProductCodes.ToArray(); - } - } - - /// <summary> - /// Compares the version string with the package version info and returns the CompareResult. - /// </summary> - /// <param name="version">Version string.</param> - /// <returns>CompareResult string.</returns> - public string CompareToVersion(string version) - { - return this.packageVersionInfo.CompareToVersion(version).ToString(); - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/SourceResult.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/SourceResult.cs @@ -1,41 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="SourceResult.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// <summary> - /// SourceResult wrapper object for displaying to PowerShell. - /// </summary> - public class SourceResult - { - /// <summary> - /// Initializes a new instance of the <see cref="SourceResult"/> class. - /// </summary> - /// <param name="catalogReference">The PackageCatalogReference COM object.</param> - public SourceResult(Management.Deployment.PackageCatalogReference catalogReference) - { - var info = catalogReference.Info; - this.Name = info.Name; - this.Argument = info.Argument; - this.Type = info.Type; - } - - /// <summary> - /// Gets the name of the source. - /// </summary> - public string Name { get; private set; } - - /// <summary> - /// Gets the argument of the source. - /// </summary> - public string Argument { get; private set; } - - /// <summary> - /// Gets the type of the source. - /// </summary> - public string Type { get; private set; } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/PSObjects/UninstallResult.cs b/src/PowerShell/Microsoft.WinGet.Client/PSObjects/UninstallResult.cs @@ -1,82 +0,0 @@ -// ----------------------------------------------------------------------------- -// <copyright file="UninstallResult.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - using System; - - /// <summary> - /// UninstallResult wrapper object for displaying to PowerShell. - /// </summary> - public class UninstallResult - { - private Management.Deployment.UninstallResult uninstallResult; - - /// <summary> - /// Initializes a new instance of the <see cref="UninstallResult"/> class. - /// </summary> - /// <param name="uninstallResult">The uninstall result COM object.</param> - public UninstallResult(Management.Deployment.UninstallResult uninstallResult) - { - this.uninstallResult = uninstallResult; - } - - /// <summary> - /// Gets the correlation data of the uninstall result. - /// </summary> - public string CorrelationData - { - get - { - return this.uninstallResult.CorrelationData; - } - } - - /// <summary> - /// Gets the extended error code exception of the failed uninstall result. - /// </summary> - public Exception ExtendedErrorCode - { - get - { - return this.uninstallResult.ExtendedErrorCode; - } - } - - /// <summary> - /// Gets a value indicating whether a reboot is required. - /// </summary> - public bool RebootRequired - { - get - { - return this.uninstallResult.RebootRequired; - } - } - - /// <summary> - /// Gets the status of the uninstall. - /// </summary> - public string Status - { - get - { - return this.uninstallResult.Status.ToString(); - } - } - - /// <summary> - /// Gets the error code of an uninstall. - /// </summary> - public uint UninstallerErrorCode - { - get - { - return this.uninstallResult.UninstallerErrorCode; - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs @@ -1,288 +0,0 @@ -//------------------------------------------------------------------------------ -// <auto-generated> -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// </auto-generated> -//------------------------------------------------------------------------------ - -namespace Microsoft.WinGet.Client.Properties { - using System; - - - /// <summary> - /// A strongly-typed resource class, for looking up localized strings, etc. - /// </summary> - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// <summary> - /// Returns the cached ResourceManager instance used by this class. - /// </summary> - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.Client.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// <summary> - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// </summary> - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// <summary> - /// 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.. - /// </summary> - internal static string AppExecutionAliasDisabledHelpMessage { - get { - return ResourceManager.GetString("AppExecutionAliasDisabledHelpMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to An error occurred while connecting to the catalog.. - /// </summary> - internal static string CatalogConnectExceptionMessage { - get { - return ResourceManager.GetString("CatalogConnectExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to An error occurred while searching for packages: {0}. - /// </summary> - internal static string FindPackagesExceptionMessage { - get { - return ResourceManager.GetString("FindPackagesExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled.. - /// </summary> - internal static string IntegrityAppExecutionAliasDisabledMessage { - get { - return ResourceManager.GetString("IntegrityAppExecutionAliasDisabledMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The App Installer is not installed.. - /// </summary> - internal static string IntegrityAppInstallerNotInstalledMessage { - get { - return ResourceManager.GetString("IntegrityAppInstallerNotInstalledMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The App Installer is not registered.. - /// </summary> - internal static string IntegrityAppInstallerNotRegisteredMessage { - get { - return ResourceManager.GetString("IntegrityAppInstallerNotRegisteredMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The App Installer does not contain the Windows Package Manager.. - /// </summary> - internal static string IntegrityAppInstallerNotSupportedMessage { - get { - return ResourceManager.GetString("IntegrityAppInstallerNotSupportedMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Windows Package Manager returned an unexcepted result.. - /// </summary> - internal static string IntegrityFailureMessage { - get { - return ResourceManager.GetString("IntegrityFailureMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The App Installer did not automatically add the PATH environment variable.. - /// </summary> - internal static string IntegrityNotInPathMessage { - get { - return ResourceManager.GetString("IntegrityNotInPathMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later.. - /// </summary> - internal static string IntegrityOsNotSupportedMessage { - get { - return ResourceManager.GetString("IntegrityOsNotSupportedMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}'. - /// </summary> - internal static string IntegrityUnexpectedVersionMessage { - get { - return ResourceManager.GetString("IntegrityUnexpectedVersionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Unable to execute winget command.. - /// </summary> - internal static string IntegrityUnknownMessage { - get { - return ResourceManager.GetString("IntegrityUnknownMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to No source matches the given value: {0}. - /// </summary> - internal static string InvalidSourceExceptionMessage { - get { - return ResourceManager.GetString("InvalidSourceExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to No versions matched the given value: {0}. - /// </summary> - internal static string InvalidVersionExceptionMessage { - get { - return ResourceManager.GetString("InvalidVersionExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Microsoft.UI.Xaml.2.7 package is not installed. - /// </summary> - internal static string MicrosoftUIXaml27Message { - get { - return ResourceManager.GetString("MicrosoftUIXaml27Message", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to No packages matched the given input criteria.. - /// </summary> - internal static string NoPackageFoundExceptionMessage { - get { - return ResourceManager.GetString("NoPackageFoundExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Installing '{0}'. - /// </summary> - internal static string ProgressRecordActivityInstalling { - get { - return ResourceManager.GetString("ProgressRecordActivityInstalling", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Uninstalling '{0}'. - /// </summary> - internal static string ProgressRecordActivityUninstalling { - get { - return ResourceManager.GetString("ProgressRecordActivityUninstalling", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Updating '{0}'. - /// </summary> - internal static string ProgressRecordActivityUpdating { - get { - return ResourceManager.GetString("ProgressRecordActivityUpdating", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA).. - /// </summary> - internal static string SingleThreadedApartmentNotSupportedMessage { - get { - return ResourceManager.GetString("SingleThreadedApartmentNotSupportedMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to User settings file is invalid.. - /// </summary> - internal static string UserSettingsReadException { - get { - return ResourceManager.GetString("UserSettingsReadException", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to {0}, {1}, and {2} other packages matched the input criteria. Please refine the input.. - /// </summary> - internal static string VagueCriteriaExceptionMessage { - get { - return ResourceManager.GetString("VagueCriteriaExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Command {0} failed with exit code {1}. - /// </summary> - internal static string WinGetCLIExceptionMessage { - get { - return ResourceManager.GetString("WinGetCLIExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Winget command run timed out: {0} {1}. - /// </summary> - internal static string WinGetCLITimeoutExceptionMessage { - get { - return ResourceManager.GetString("WinGetCLITimeoutExceptionMessage", resourceCulture); - } - } - - /// <summary> - /// Looks up a localized string similar to Windows Package Manager not supported.. - /// </summary> - internal static string WinGetNotSupportedMessage { - get { - return ResourceManager.GetString("WinGetNotSupportedMessage", resourceCulture); - } - } - } -} diff --git a/src/PowerShell/Microsoft.WinGet.Client/README.md b/src/PowerShell/Microsoft.WinGet.Client/README.md @@ -1,10 +1,53 @@ # Windows Package Manager PowerShell Module -This project contains the source code for building the Windows Package Manager PowerShell Module. +The Windows Package Manager PowerShell Module is made up on three components + +1. Generated functions using `Crescendo` +2. The `Microsoft.WinGet.Client.Cmdlets` project which contains cmdlet implementations. +3. The `Microsoft.WinGet.Client.Engine` project which contain the real logic for the cmdlets. ## Building the PowerShell Module Locally -After building the project solution, the `Microsoft.WinGet.Client` PowerShell module can be found in the output directory in the `PowerShell` folder. For example if you built the project as x64 release, you should expect to find the module files in `$(SolutionDirectory)/src/x64/Release/PowerShell`. +After building the Microsoft.WinGet.Client.Cmdlets project, the `Microsoft.WinGet.Client` PowerShell module can be found in the output directory in the `PowerShell` folder. For example if you built the project as x64 release, you should expect to find the module files in `$(SolutionDirectory)/src/x64/Release/PowerShell`. + +This project has after build targets that will copy all the necessary files in the correct location. + +## Adding a new function + +We don't have an automatic way of producing the psm1 from Crescendo. If a new function is going to be added: +1. Modify `Crescendo\Crescendo.json` +2. Run `Crescendo\Create-CrescendoFunctions.ps1` +3. Copy the new psm1 in `ModulesFiles\Microsoft.WinGet.Client.psm1` +4. Add new function in `ModulesFiles\Microsoft.WinGet.Client.psd1` + +## Adding a new cmdlet + +This project uses a custom `AssemblyLoadContext` that handles all dependencies loading. The only two binaries that are loaded in the default context are Microsoft.WinGet.Client.Cmdlets.dll and Microsoft.WinGet.Client.Engine.dll. This is to handle [assembly dependency conflicts](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7.3). Because of that, the cmdlet must be defined in Microsoft.WinGet.Client.Cmdlets but the actual implementation in Microsoft.WinGet.Client.Engine. + +If the new cmdlet introduces a new dependency, please make sure to add it in the after build targets to copy it in the Dependencies directory. + +## Functions +- Add-WinGetSource +- Disable-WinGetSetting +- Enable-WinGetSetting +- Get-WinGetSettings +- Remove-WinGetSource +- Reset-WinGetSource + +## Cmdlets +- Assert-WinGetPackageManager +- Find-WinGetPackage +- Get-WinGetPackage +- Get-WinGetSource +- Get-WinGetUserSettings +- Get-WinGetVersion +- Install-WinGetPackage +- Repair-WinGetPackageManager +- Set-WinGetUserSettings +- Test-WinGetUserSettings +- Uninstall-WinGetPackage +- Update-WinGetPackage + ## Prerequisites @@ -27,18 +70,3 @@ Import the module manifest (Microsoft.WinGet.Client.psd1) by running the followi ``` Import-Module <Path to Microsoft.WinGet.Client.psd1> ``` - -The following cmdlets and functions are available for you to try: - - Get-WinGetVersion - Enable-WinGetSetting - Disable-WinGetSetting - Add-WinGetSource - Remove-WinGetSource - Reset-WinGetSource - Find-WinGetPackage - Get-WinGetPackage - Get-WinGetSource - Install-WinGetPackage - Uninstall-WinGetPackage - Update-WinGetPackage- \ No newline at end of file diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -20,10 +20,12 @@ [CmdletBinding()] param ( [Parameter(Mandatory)] + [ValidateSet("x64", "x86")] [string] $Platform, [Parameter(Mandatory)] + [ValidateSet("Debug", "Release")] [string] $Configuration, @@ -49,18 +51,26 @@ class WinGetModule # I know it makes sense, but please don't do a clean up of $moduleRootOutput. When the modules are loaded # there's no way to tell PowerShell to release the binary dlls that are loaded. -$moduleRootOutput = "$PSScriptRoot\Module\" +$local:moduleRootOutput = "$PSScriptRoot\Module\" # Add here new modules -[WinGetModule[]]$modules = - [WinGetModule]::new("Microsoft.WinGet.DSC", "$PSScriptRoot\..\Microsoft.WinGet.DSC\", $false, $false), - [WinGetModule]::new("Microsoft.WinGet.Client", "$PSScriptRoot\..\Microsoft.WinGet.Client\Module\", $true, $true) +[WinGetModule[]]$local:modules = + [WinGetModule]::new( + "Microsoft.WinGet.DSC", + "$PSScriptRoot\..\Microsoft.WinGet.DSC\", + $false, + $false), + [WinGetModule]::new( + "Microsoft.WinGet.Client", + "$PSScriptRoot\..\Microsoft.WinGet.Client\ModuleFiles\", + $true, + $true) foreach($module in $modules) { # Import-Module with Force just changes functions in the root module, not any nested ones. There's no way to load any # updated classes. To ensure that you are running the latest version run Remove-Module - if (Get-Module -ListAvailable -Name $module.Name) + if (Get-Module -Name $module.Name) { Write-Host "Removing module $($module.Name)" -ForegroundColor Green Remove-Module $module.Name -Force @@ -70,13 +80,13 @@ foreach($module in $modules) if ($module.HasBinary) { # Copy output files from VS. - Write-Host "Coping binary module $($module.Name)" -ForegroundColor Green + Write-Host "Copying binary module $($module.Name)" -ForegroundColor Green xcopy "$PSScriptRoot\..\..\$Platform\$Configuration\PowerShell\$($module.Name)\" "$moduleRootOutput\$($module.Name)\" /d /s /f /y } # Copy PowerShell files even for modules with binary resources. # VS won't update the files if there's nothing to build... - Write-Host "Coping module $($module.Name)" -ForegroundColor Green + Write-Host "Copying module $($module.Name)" -ForegroundColor Green xcopy $module.ModuleRoot "$moduleRootOutput\$($module.Name)\" /d /s /f /y if ($module.ForceWinGetDev)