winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 7b28fb42cb7a15d5f9145155ea73c410c7146599
parent 030d998bc0a11d9ecffc49d8f22818a00954e07b
Author: Ryan <69221034+ryfu-msft@users.noreply.github.com>
Date:   Sat, 17 Feb 2024 17:37:18 -0800

Implement Export-WinGetPackage powershell cmdlet for `winget download` (#3977)


Diffstat:
M.github/actions/spelling/excludes.txt | 1+
Msrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs | 10+++++-----
Msrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs | 6------
Asrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ExportPackageCmdlet.cs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs | 81++++++++++++++++++++++++++++++++++---------------------------------------------
Asrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageFieldMatchOption.cs -> src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageFieldMatchOption.cs | 0
Rsrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallMode.cs -> src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallMode.cs | 0
Rsrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallScope.cs -> src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallScope.cs | 0
Asrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageUninstallMode.cs -> src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageUninstallMode.cs | 0
Rsrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSProcessorArchitecture.cs -> src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSProcessorArchitecture.cs | 0
Msrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs | 10++++++++--
Msrc/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs | 5+++--
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs | 16+++++++++++++---
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs | 7+------
Asrc/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs | 51+++++++++++++++++++++++++++++++++++++++++----------
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs | 13++++++++++---
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs | 1-
Asrc/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs | 42++++++++++++++++++++++++++++++++++++++++++
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs | 2+-
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs | 34++++++++++++++++++++++++++++++++++
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs | 13+++++++++++++
Asrc/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs | 9+++++++++
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx | 4++++
Msrc/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 | 1+
Msrc/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
30 files changed, 816 insertions(+), 85 deletions(-)

diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt @@ -89,6 +89,7 @@ ^src/Xlang/ ^src/YamlCppLib/ # Because it doesn't handle argument -Words well +^src/PowerShell/tests/ ^tools/CorrelationTestbed/.*\.ps1$ ^tools/COMTrace/ComTrace.wprp$ ignore$ diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs @@ -14,7 +14,7 @@ namespace Microsoft.WinGet.Client.Commands.Common /// 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 + public abstract class InstallCmdlet : InstallerSelectionCmdlet { private string location; @@ -49,13 +49,13 @@ namespace Microsoft.WinGet.Client.Commands.Common ? value : this.SessionState.Path.CurrentFileSystemLocation + @"\" + value; } - } - + } + /// <summary> - /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// Gets or sets the path to the logging file. /// </summary> [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AllowHashMismatch { get; set; } + public string Log { get; set; } /// <summary> /// Gets or sets a value indicating whether to continue upon non security related failures. diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs @@ -42,11 +42,5 @@ namespace Microsoft.WinGet.Client.Commands.Common /// </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/ExportPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ExportPackageCmdlet.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ExportPackageCmdlet.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> + /// Downloads a package installer from the pipeline or from a configured source. + /// </summary> + [Cmdlet( + VerbsData.Export, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSDownloadResult))] + public sealed class ExportPackageCmdlet : InstallerSelectionCmdlet + { + private DownloadCommand command = null; + + /// <summary> + /// Gets or sets the directory where the installer will be downloaded to. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string DownloadDirectory { get; set; } + + /// <summary> + /// Installs a package from the pipeline or from a configured source. + /// </summary> + protected override void ProcessRecord() + { + this.command = new DownloadCommand( + this, + this.PSCatalogPackage, + this.Version, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.AllowHashMismatch.ToBool(), + this.SkipDependencies.ToBool(), + this.Locale); + this.command.Download(this.DownloadDirectory, this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.InstallerType.ToString()); + } + + /// <summary> + /// Interrupts currently running code within the command. + /// </summary> + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs @@ -1,47 +1,34 @@ -// ----------------------------------------------------------------------------- -// <copyright file="InstallPackageCmdlet.cs" company="Microsoft Corporation"> -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// </copyright> -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ +// ----------------------------------------------------------------------------- +// <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.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Client.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 - { - private InstallerPackageCommand command = null; - - /// <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() + /// <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 + { + private InstallerPackageCommand command = null; + + /// <summary> + /// Installs a package from the pipeline or from a configured source. + /// </summary> + protected override void ProcessRecord() { this.command = new InstallerPackageCommand( this, @@ -58,8 +45,10 @@ namespace Microsoft.WinGet.Client.Commands this.Name, this.Moniker, this.Source, - this.Query); - this.command.Install(this.Scope.ToString(), this.Architecture.ToString(), this.MatchOption.ToString(), this.Mode.ToString()); + this.Query, + this.SkipDependencies.ToBool()); + + this.command.Install(this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.Mode.ToString(), this.InstallerType.ToString()); } /// <summary> @@ -71,6 +60,6 @@ namespace Microsoft.WinGet.Client.Commands { this.command.Cancel(); } - } - } -} + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------------- +// <copyright file="InstallerSelectionCmdlet.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.PSObjects; + + /// <summary> + /// This is the base class for all commands that select an installer from a given package. + /// Contains shared arguments for the install, update, and download commands. + /// </summary> + public abstract class InstallerSelectionCmdlet : PackageCmdlet + { + /// <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 the architecture of the installer to be downloaded. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSProcessorArchitecture Architecture { get; set; } = PSProcessorArchitecture.Default; + + /// <summary> + /// Gets or sets the installer type to be downloaded. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageInstallerType InstallerType { get; set; } = PSPackageInstallerType.Default; + + /// <summary> + /// Gets or sets the locale of the installer to be downloaded. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Locale { get; set; } + + /// <summary> + /// Gets or sets the scope of the installer to be downloaded. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageInstallScope Scope { get; set; } = PSPackageInstallScope.Any; + + /// <summary> + /// Gets or sets a value indicating whether skip dependencies. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter SkipDependencies { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageFieldMatchOption.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageFieldMatchOption.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallMode.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageInstallScope.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallScope.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSPackageInstallerType.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// <summary> + /// The installer type of the package. + /// </summary> + public enum PSPackageInstallerType + { + /// <summary> + /// Let winget decide. + /// </summary> + Default, + + /// <summary> + /// Inno, + /// </summary> + Inno, + + /// <summary> + /// Wix. + /// </summary> + Wix, + + /// <summary> + /// Msi. + /// </summary> + Msi, + + /// <summary> + /// Nullsoft. + /// </summary> + Nullsoft, + + /// <summary> + /// Zip. + /// </summary> + Zip, + + /// <summary> + /// Msix. + /// </summary> + Msix, + + /// <summary> + /// Exe. + /// </summary> + Exe, + + /// <summary> + /// Burn. + /// </summary> + Burn, + + /// <summary> + /// MSStore, + /// </summary> + MSStore, + + /// <summary> + /// Portable. + /// </summary> + Portable, + } +}+ \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSPackageUninstallMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageUninstallMode.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/PSObjects/PSProcessorArchitecture.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSProcessorArchitecture.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs @@ -36,7 +36,13 @@ namespace Microsoft.WinGet.Client.Commands /// Gets or sets a value indicating whether to continue upon non security related failures. /// </summary> [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } + public SwitchParameter Force { get; set; } + + /// <summary> + /// Gets or sets the path to the logging file. + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Log { get; set; } /// <summary> /// Uninstalls a package from the local system. @@ -53,7 +59,7 @@ namespace Microsoft.WinGet.Client.Commands this.Moniker, this.Source, this.Query); - this.command.Uninstall(this.Mode.ToString(), this.MatchOption.ToString(), this.Force.ToBool()); + this.command.Uninstall(this.MatchOption.ToString(), this.Mode.ToString(), this.Force.ToBool()); } /// <summary> diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs @@ -49,8 +49,9 @@ namespace Microsoft.WinGet.Client.Commands this.Name, this.Moniker, this.Source, - this.Query); - command.Update(this.IncludeUnknown.ToBool(), this.MatchOption.ToString(), this.Mode.ToString()); + this.Query, + this.SkipDependencies); + command.Update(this.IncludeUnknown.ToBool(), this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.Mode.ToString(), this.InstallerType.ToString()); } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs @@ -9,7 +9,6 @@ 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 @@ -27,6 +26,16 @@ namespace Microsoft.WinGet.Client.Engine.Commands.Common } /// <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 skip dependencies. + /// </summary> + protected bool SkipDependencies { get; set; } + + /// <summary> /// Gets or sets the override arguments to be passed on to the installer. /// </summary> protected string? Override { get; set; } @@ -42,9 +51,9 @@ namespace Microsoft.WinGet.Client.Engine.Commands.Common protected string? Location { get; set; } /// <summary> - /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// Gets or sets the path to the logging file. /// </summary> - protected bool AllowHashMismatch { get; set; } + protected string? Log { get; set; } /// <summary> /// Gets or sets a value indicating whether to continue upon non security related failures. @@ -69,6 +78,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands.Common { InstallOptions options = ManagementDeploymentFactory.Instance.CreateInstallOptions(); options.AllowHashMismatch = this.AllowHashMismatch; + options.SkipDependencies = this.SkipDependencies; options.Force = this.Force; options.PackageInstallMode = PSEnumHelpers.ToPackageInstallMode(mode); if (version != null) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs @@ -18,7 +18,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands.Common /// <summary> /// This is the base class for commands which operate on a specific package and version i.e., - /// the "install", "uninstall", and "upgrade" commands. + /// the "install", "uninstall", "download", and "upgrade" commands. /// </summary> public abstract class PackageCommand : FinderCommand { @@ -45,11 +45,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands.Common 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> /// <typeparam name="TResult">Type of callback's result.</typeparam> diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs @@ -0,0 +1,165 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DownloadCommand.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 System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// <summary> + /// Downloads a package installer. + /// </summary> + public sealed class DownloadCommand : PackageCommand + { + /// <summary> + /// Initializes a new instance of the <see cref="DownloadCommand"/> class. + /// </summary> + /// <param name="psCmdlet">Caller cmdlet.</param> + /// <param name="psCatalogPackage">PSCatalogPackage.</param> + /// <param name="version">Version to install.</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="allowHashMismatch">To skip the installer hash validation check.</param> + /// <param name="skipDependencies">To skip package dependencies.</param> + /// <param name="locale">Locale of the package.</param> + public DownloadCommand( + PSCmdlet psCmdlet, + PSCatalogPackage psCatalogPackage, + string version, + string id, + string name, + string moniker, + string source, + string[] query, + bool allowHashMismatch, + bool skipDependencies, + string locale) + : base(psCmdlet) + { + // PackageCommand + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage; + } + + this.Version = version; + + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + // DownloadCommand + this.AllowHashMismatch = allowHashMismatch; + this.SkipDependencies = skipDependencies; + this.Locale = locale; + } + + /// <summary> + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// </summary> + private bool AllowHashMismatch { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to skip dependencies. + /// </summary> + private bool SkipDependencies { get; set; } + + /// <summary> + /// Gets or sets the locale to install. + /// </summary> + private string? Locale { get; set; } + + /// <summary> + /// Process download package. + /// </summary> + /// <param name="downloadDirectory">The target directory where the installer will be downloaded to.</param> + /// <param name="psPackageFieldMatchOption">PSPackageFieldMatchOption.</param> + /// <param name="psPackageInstallScope">PSPackageInstallScope.</param> + /// <param name="psProcessorArchitecture">PSProcessorArchitecture.</param> + /// <param name="psPackageInstallerType">PSPackageInstallerType.</param> + public void Download( + string downloadDirectory, + string psPackageFieldMatchOption, + string psPackageInstallScope, + string psProcessorArchitecture, + string psPackageInstallerType) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + DownloadOptions options = this.GetDownloadOptions(version); + + if (!string.IsNullOrEmpty(downloadDirectory)) + { + options.DownloadDirectory = downloadDirectory; + } + + if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) + { + options.Architecture = PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture); + } + + if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) + { + options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); + } + + options.Scope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + + return await this.DownloadPackageAsync(package, options); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSDownloadResult(result.Item1, result.Item2)); + } + } + + private DownloadOptions GetDownloadOptions(PackageVersionId? version) + { + var options = ManagementDeploymentFactory.Instance.CreateDownloadOptions(); + if (version != null) + { + options.PackageVersionId = version; + } + + if (this.Locale != null) + { + options.Locale = this.Locale; + } + + options.AllowHashMismatch = this.AllowHashMismatch; + options.SkipDependencies = this.SkipDependencies; + + return options; + } + + private async Task<DownloadResult> DownloadPackageAsync( + CatalogPackage package, + DownloadOptions options) + { + var activity = string.Format(Resources.ProgressRecordActivityExporting, package.Name); + var progressOperation = new DownloadOperationWithProgress(this, activity); + return await progressOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.DownloadPackageAsync(package, options)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs @@ -27,7 +27,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands /// <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="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> @@ -38,12 +38,13 @@ namespace Microsoft.WinGet.Client.Engine.Commands /// <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="skipDependencies">To skip package dependencies.</param> public InstallerPackageCommand( PSCmdlet psCmdlet, string @override, string custom, string location, - bool allowHashMisMatch, + bool allowHashMismatch, bool force, string header, PSCatalogPackage psCatalogPackage, @@ -53,16 +54,19 @@ namespace Microsoft.WinGet.Client.Engine.Commands string name, string moniker, string source, - string[] query) + string[] query, + bool skipDependencies) : base(psCmdlet) { // InstallCommand. this.Override = @override; this.Custom = custom; this.Location = location; - this.AllowHashMismatch = allowHashMisMatch; this.Force = force; this.Header = header; + this.AllowHashMismatch = allowHashMismatch; + this.SkipDependencies = skipDependencies; + this.Log = log; // PackageCommand. if (psCatalogPackage != null) @@ -71,7 +75,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands } this.Version = version; - this.Log = log; // FinderCommand this.Id = id; @@ -84,15 +87,17 @@ namespace Microsoft.WinGet.Client.Engine.Commands /// <summary> /// Process install package command. /// </summary> + /// <param name="psPackageFieldMatchOption">PSPackageFieldMatchOption.</param> /// <param name="psPackageInstallScope">PSPackageInstallScope.</param> /// <param name="psProcessorArchitecture">PSProcessorArchitecture.</param> - /// <param name="psPackageFieldMatchOption">PSPackageFieldMatchOption.</param> /// <param name="psPackageInstallMode">PSPackageInstallMode.</param> + /// <param name="psPackageInstallerType">PSPackageInstallerType.</param> public void Install( + string psPackageFieldMatchOption, string psPackageInstallScope, string psProcessorArchitecture, - string psPackageFieldMatchOption, - string psPackageInstallMode) + string psPackageInstallMode, + string psPackageInstallerType) { var result = this.Execute( async () => await this.GetPackageAndExecuteAsync( @@ -101,13 +106,19 @@ namespace Microsoft.WinGet.Client.Engine.Commands async (package, version) => { InstallOptions options = this.GetInstallOptions(version, psPackageInstallMode); - if (psProcessorArchitecture != "Default") + if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) { options.AllowedArchitectures.Clear(); options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); } + if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) + { + options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); + } + options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + return await this.InstallPackageAsync(package, options); })); @@ -122,11 +133,17 @@ namespace Microsoft.WinGet.Client.Engine.Commands /// </summary> /// <param name="includeUnknown">If updating to an unknown version is allowed.</param> /// <param name="psPackageFieldMatchOption">PSPackageFieldMatchOption.</param> + /// <param name="psPackageInstallScope">PSPackageInstallScope.</param> + /// <param name="psProcessorArchitecture">PSProcessorArchitecture.</param> /// <param name="psPackageInstallMode">PSPackageInstallMode.</param> + /// <param name="psPackageInstallerType">PSPackageInstallerType.</param> public void Update( bool includeUnknown, string psPackageFieldMatchOption, - string psPackageInstallMode) + string psPackageInstallScope, + string psProcessorArchitecture, + string psPackageInstallMode, + string psPackageInstallerType) { var result = this.Execute( async () => await this.GetPackageAndExecuteAsync( @@ -136,6 +153,20 @@ namespace Microsoft.WinGet.Client.Engine.Commands { InstallOptions options = this.GetInstallOptions(version, psPackageInstallMode); options.AllowUpgradeToUnknownVersion = includeUnknown; + + if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) + { + options.AllowedArchitectures.Clear(); + options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); + } + + if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) + { + options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); + } + + options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + return await this.UpgradePackageAsync(package, options); })); diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs @@ -51,7 +51,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands } this.Version = version; - this.Log = log; // FinderCommand this.Id = id; @@ -59,17 +58,25 @@ namespace Microsoft.WinGet.Client.Engine.Commands this.Moniker = moniker; this.Source = source; this.Query = query; + + // UninstallPackageCommand + this.Log = log; } /// <summary> + /// Gets or sets the path to the logging file. + /// </summary> + private string? Log { get; set; } + + /// <summary> /// Process uninstall package. /// </summary> - /// <param name="psPackageUninstallMode">PSPackageUninstallMode.</param> /// <param name="psPackageFieldMatchOption">PSPackageFieldMatchOption.</param> + /// <param name="psPackageUninstallMode">PSPackageUninstallMode.</param> /// <param name="force">Force.</param> public void Uninstall( - string psPackageUninstallMode, string psPackageFieldMatchOption, + string psPackageUninstallMode, bool force) { var result = this.Execute( diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs @@ -8,7 +8,6 @@ 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; diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------------- +// <copyright file="DownloadOperationWithProgress.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using Windows.Foundation; + + /// <summary> + /// Handler progress for package download. + /// </summary> + internal class DownloadOperationWithProgress : OperationWithProgressBase<DownloadResult, PackageDownloadProgress> + { + /// <summary> + /// Initializes a new instance of the <see cref="DownloadOperationWithProgress"/> class. + /// </summary> + /// <param name="pwshCmdlet">A <see cref="PowerShellCmdlet" /> instance.</param> + /// <param name="activity">Activity.</param> + public DownloadOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) + : base(pwshCmdlet, activity) + { + } + + /// <inheritdoc/> + public override void Progress(IAsyncOperationWithProgress<DownloadResult, PackageDownloadProgress> operation, PackageDownloadProgress progress) + { + ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + record.StatusDescription = Resources.DownloadingMessage; + record.PercentComplete = (int)(progress.DownloadProgress * 100); + this.PwshCmdlet.Write(StreamType.Progress, record); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -60,7 +60,7 @@ namespace Microsoft.WinGet.Client.Engine.Helpers 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"); - private static readonly Guid DownloadOptionsIid = Guid.Parse("B4D72A63-40FF-597D-A7DA-43580268DC96"); + private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); private static readonly IEnumerable<Architecture> ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs @@ -16,6 +16,16 @@ namespace Microsoft.WinGet.Client.Engine.Helpers internal static class PSEnumHelpers { /// <summary> + /// Checks if the provided enum string value matches the 'Default' value for PS Enums. + /// </summary> + /// <param name="value">Enum string value.</param> + /// <returns>Boolean value.</returns> + public static bool IsDefaultEnum(string value) + { + return string.Equals(value, "Default", StringComparison.OrdinalIgnoreCase); + } + + /// <summary> /// Converts PSPackageInstallMode string value to PackageInstallMode. /// </summary> /// <param name="value">PSPackageInstallMode to string value.</param> @@ -98,5 +108,29 @@ namespace Microsoft.WinGet.Client.Engine.Helpers _ => throw new InvalidOperationException(), }; } + + /// <summary> + /// Converts PSPackageInstallerType string value to PackageInstallerType. + /// </summary> + /// <param name="value">PSPackageInstallerType string value.</param> + /// <returns>PackageInstallerType.</returns> + public static PackageInstallerType ToPackageInstallerType(string value) + { + return value switch + { + "Unknown" => PackageInstallerType.Unknown, + "Inno" => PackageInstallerType.Inno, + "Wix" => PackageInstallerType.Wix, + "Msi" => PackageInstallerType.Msi, + "Nullsoft" => PackageInstallerType.Nullsoft, + "Zip" => PackageInstallerType.Zip, + "Msix" => PackageInstallerType.Msix, + "Exe" => PackageInstallerType.Exe, + "Burn" => PackageInstallerType.Burn, + "MSStore" => PackageInstallerType.MSStore, + "Portable" => PackageInstallerType.Portable, + _ => throw new InvalidOperationException(), + }; + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -76,6 +76,19 @@ namespace Microsoft.WinGet.Client.Engine.Helpers } /// <summary> + /// Wrapper for DownloadPackageAsync. + /// </summary> + /// <param name="package">The package to download.</param> + /// <param name="options">The download options.</param> + /// <returns>An async operation with progress.</returns> + public IAsyncOperationWithProgress<DownloadResult, PackageDownloadProgress> DownloadPackageAsync(CatalogPackage package, DownloadOptions options) + { + return this.Execute( + () => this.packageManager.DownloadPackageAsync(package, options), + false); + } + + /// <summary> /// Wrapper for GetPackageCatalogs. /// </summary> /// <returns>A list of PackageCatalogReferences.</returns> diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs @@ -0,0 +1,115 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PSDownloadResult.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> + /// PSDownloadResult. + /// </summary> + public sealed class PSDownloadResult + { + private readonly DownloadResult downloadResult; + private readonly CatalogPackage catalogPackage; + + /// <summary> + /// Initializes a new instance of the <see cref="PSDownloadResult"/> class. + /// </summary> + /// <param name="downloadResult">The download result COM object.</param> + /// <param name="catalogPackage">The catalog package COM object.</param> + internal PSDownloadResult(DownloadResult downloadResult, CatalogPackage catalogPackage) + { + this.downloadResult = downloadResult; + this.catalogPackage = catalogPackage; + } + + /// <summary> + /// Gets the id of the downloaded package. + /// </summary> + public string Id + { + get + { + return this.catalogPackage.Id; + } + } + + /// <summary> + /// Gets the name of the downloaded package. + /// </summary> + public string Name + { + get + { + return this.catalogPackage.Name; + } + } + + /// <summary> + /// Gets the source name of the downloaded package. + /// </summary> + public string Source + { + get + { + return this.catalogPackage.DefaultInstallVersion.PackageCatalog.Info.Name; + } + } + + /// <summary> + /// Gets the correlation data of the downloaded result. + /// </summary> + public string CorrelationData + { + get + { + return this.downloadResult.CorrelationData; + } + } + + /// <summary> + /// Gets the extended error code exception of the failed download result. + /// </summary> + public Exception ExtendedErrorCode + { + get + { + return this.downloadResult.ExtendedErrorCode; + } + } + + /// <summary> + /// Gets the status of the download. + /// </summary> + public string Status + { + get + { + return this.downloadResult.Status.ToString(); + } + } + + /// <summary> + /// If the download succeeded. + /// </summary> + /// <returns>True if installation succeeded.</returns> + public bool Succeeded() + { + return this.downloadResult.Status == DownloadResultStatus.Ok; + } + + /// <summary> + /// Message with error information. + /// </summary> + /// <returns>Error message.</returns> + public string ErrorMessage() + { + return $"DownloadStatus '{this.Status}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -232,6 +232,15 @@ namespace Microsoft.WinGet.Resources { } /// <summary> + /// Looks up a localized string similar to Exporting &apos;{0}&apos;. + /// </summary> + internal static string ProgressRecordActivityExporting { + get { + return ResourceManager.GetString("ProgressRecordActivityExporting", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Installing &apos;{0}&apos;. /// </summary> internal static string ProgressRecordActivityInstalling { diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -240,4 +240,8 @@ <value>Cannot find asset {0}</value> <comment>{Locked="{0}"} {0} - The asset name</comment> </data> + <data name="ProgressRecordActivityExporting" xml:space="preserve"> + <value>Exporting '{0}'</value> + <comment>{Locked="{0}"} {0} - The name of the package being exported.</comment> + </data> </root> \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml @@ -216,6 +216,58 @@ </TableControl> </View> <View> + <Name>Microsoft.WinGet.Client.Engine.PSObjects.PSDownloadResult</Name> + <ViewSelectedBy> + <TypeName>Microsoft.WinGet.Client.Engine.PSObjects.PSDownloadResult</TypeName> + </ViewSelectedBy> + <TableControl> + <TableHeaders> + <TableColumnHeader> + <Label>Id</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Name</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Source</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>Status</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>ExtendedErrorCode</Label> + </TableColumnHeader> + <TableColumnHeader> + <Label>CorrelationData</Label> + </TableColumnHeader> + </TableHeaders> + <TableRowEntries> + <TableRowEntry> + <TableColumnItems> + <TableColumnItem> + <ScriptBlock>$_.Id</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Name</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Source</ScriptBlock> + </TableColumnItem> + <TableColumnItem> + <ScriptBlock>$_.Status</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> diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 @@ -95,6 +95,7 @@ CmdletsToExport = @( 'Add-WinGetSource' 'Remove-WinGetSource' 'Reset-WinGetSource' + 'Export-WinGetPackage' ) # Variables to export from this module diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -118,6 +118,11 @@ BeforeAll { } } } + + function GetRandomTestDirectory() + { + return Join-Path -Path $env:Temp -ChildPath "WingetPwshTest-$(New-Guid)" + } } Describe 'Get-WinGetVersion' { @@ -350,6 +355,75 @@ Describe 'Get-WinGetPackage' { } } +Describe 'Export-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Download by Id' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + # Download directory should be created and have exactly two files (installer and manifest file). + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by Locale' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -Locale 'zh-CN' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" + $result.Name | Should -Be "TestMultipleInstallers" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by InstallerType' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -InstallerType 'msi' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" + $result.Name | Should -Be "TestMultipleInstallers" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by InstallerType that does not exist' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -InstallerType 'zip' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'NoApplicableInstallers' + $result.ExtendedErrorCode | Should -Not -BeNullOrEmpty + Test-Path -Path $testDirectory | Should -Be $false + } + + AfterEach { + if (Test-Path $testDirectory) { + Remove-Item $testDirectory -Force -Recurse + } + } +} + Describe 'Get-WinGetUserSettings' { It 'Get settings' {