diff --git a/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1 b/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1 index 1e81dc6aa8..6f5c65f7d1 100644 --- a/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1 +++ b/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1 @@ -56,7 +56,7 @@ $commandOptions = @{ config = "--name='' --value=''" feature = "--name=''" apikey = "--source='' --api-key='' --remove" - export = "--include-version-numbers --output-file-path=''" + export = "--include-version-numbers --output-file-path='' --include-remembered-arguments --exclude-pins" template = "--name=''" cache = "--expired" rule = "--name=''" diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyExportCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyExportCommandSpecs.cs index b5b1b7254a..73d49c0283 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyExportCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyExportCommandSpecs.cs @@ -37,16 +37,20 @@ public abstract class ChocolateyExportCommandSpecsBase : TinySpec protected Mock NugetService = new Mock(); protected Mock FileSystem = new Mock(); protected ChocolateyConfiguration Configuration = new ChocolateyConfiguration(); + protected Mock PackageInfoService = new Mock(); + protected Mock PackageService = new Mock(); public override void Context() { - Command = new ChocolateyExportCommand(NugetService.Object, FileSystem.Object); + Command = new ChocolateyExportCommand(NugetService.Object, FileSystem.Object, PackageInfoService.Object, PackageService.Object); } public void Reset() { NugetService.ResetCalls(); FileSystem.ResetCalls(); + PackageInfoService.ResetCalls(); + PackageService.ResetCalls(); } } @@ -104,6 +108,24 @@ public void Should_add_include_version_to_the_option_set() { _optionSet.Contains("include-version").Should().BeTrue(); } + + [Fact] + public void Should_add_include_arguments_to_the_option_set() + { + _optionSet.Contains("include-arguments").Should().BeTrue(); + } + + [Fact] + public void Should_add_include_remembered_arguments_to_the_option_set() + { + _optionSet.Contains("include-remembered-arguments").Should().BeTrue(); + } + + [Fact] + public void Should_add_exclude_pins_to_the_option_set() + { + _optionSet.Contains("exclude-pins").Should().BeTrue(); + } } public class When_handling_additional_argument_parsing : ChocolateyExportCommandSpecsBase diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index 3d7be60ae5..15d87c37ba 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -420,11 +420,16 @@ private static void SetGlobalOptions(IList args, ChocolateyConfiguration if (timeout > 0 || timeoutString.IsEqualTo("0")) { config.CommandExecutionTimeoutSeconds = timeout; + config.CommandExecutionTimeoutSecondsArgumentWasPassed = true; } }) .Add("c=|cache=|cachelocation=|cache-location=", "CacheLocation - Location for download cache, defaults to %TEMP% or value in chocolatey.config file.", - option => config.CacheLocation = option.UnquoteSafe()) + option => + { + config.CacheLocation = option.UnquoteSafe(); + config.CacheLocationArgumentWasPassed = true; + }) .Add("allowunofficial|allow-unofficial|allowunofficialbuild|allow-unofficial-build", "AllowUnofficialBuild - When not using the official build you must set this flag for choco to continue.", option => config.AllowUnofficialBuild = option != null) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs index 11f956a1f6..4b4cb5b32f 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs @@ -20,6 +20,7 @@ using System.IO; using System.Text; using System.Xml; +using System.Xml.Serialization; using chocolatey.infrastructure.app.attributes; using chocolatey.infrastructure.commandline; using chocolatey.infrastructure.app.configuration; @@ -36,11 +37,19 @@ public class ChocolateyExportCommand : ICommand { private readonly INugetService _nugetService; private readonly IFileSystem _fileSystem; - - public ChocolateyExportCommand(INugetService nugetService, IFileSystem fileSystem) + private readonly IChocolateyPackageInformationService _packageInfoService; + private readonly IChocolateyPackageService _packageService; + + public ChocolateyExportCommand( + INugetService nugetService, + IFileSystem fileSystem, + IChocolateyPackageInformationService packageInfoService, + IChocolateyPackageService packageService) { _nugetService = nugetService; _fileSystem = fileSystem; + _packageInfoService = packageInfoService; + _packageService = packageService; } public void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfiguration configuration) @@ -52,6 +61,12 @@ public void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfiguration .Add("include-version-numbers|include-version", "Include Version Numbers - controls whether or not version numbers for each package appear in generated file. Defaults to false.", option => configuration.ExportCommand.IncludeVersionNumbers = option != null) + .Add("include-arguments|include-remembered-arguments", + "Include Remembered Arguments - controls whether or not remembered arguments for each package appear in generated file. Defaults to false. Available in 2.3.0+", + option => configuration.ExportCommand.IncludeRememberedPackageArguments = option != null) + .Add("exclude-pins", + "Exclude Pins - controls whether or not pins are included. Only applies if remembered arguments are exported. Defaults to false. Available in 2.4.0+", + option => configuration.ExportCommand.ExcludePins = option != null) ; } @@ -95,12 +110,15 @@ choco export [] "chocolatey".Log().Info(@" choco export choco export --include-version-numbers + choco export --include-version-numbers --include-remembered-arguments + choco export --include-remembered-arguments --exclude-pins choco export ""'c:\temp\packages.config'"" choco export ""'c:\temp\packages.config'"" --include-version-numbers choco export -o=""'c:\temp\packages.config'"" choco export -o=""'c:\temp\packages.config'"" --include-version-numbers choco export --output-file-path=""'c:\temp\packages.config'"" choco export --output-file-path=""'c:\temp\packages.config'"" --include-version-numbers + choco export --output-file-path=""""'c:\temp\packages.config'"""" --include-remembered-arguments NOTE: See scripting in the command reference (`choco -?`) for how to write proper scripts and integrations. @@ -131,39 +149,90 @@ public bool MayRequireAdminAccess() public void DryRun(ChocolateyConfiguration configuration) { - this.Log().Info("Export would have been with options: {0} Output File Path={1}{0} Include Version Numbers:{2}".FormatWith(Environment.NewLine, configuration.ExportCommand.OutputFilePath, configuration.ExportCommand.IncludeVersionNumbers)); + this.Log().Info("Export would have been with options: {0} Output File Path={1}{0} Include Version Numbers:{2}{0} Include Remembered Arguments: {3}{0} Exclude Pins: {4}".FormatWith( + Environment.NewLine, + configuration.ExportCommand.OutputFilePath, + configuration.ExportCommand.IncludeVersionNumbers, + configuration.ExportCommand.IncludeRememberedPackageArguments, + configuration.ExportCommand.ExcludePins)); } public void Run(ChocolateyConfiguration configuration) { - var packageResults = _nugetService.GetInstalledPackages(configuration); - var settings = new XmlWriterSettings { Indent = true, Encoding = new UTF8Encoding(false) }; + var installedPackages = _nugetService.GetInstalledPackages(configuration); + var xmlWriterSettings = new XmlWriterSettings { Indent = true, Encoding = new UTF8Encoding(false) }; + + configuration.CreateBackup(); FaultTolerance.TryCatchWithLoggingException( () => { + var packagesConfig = new PackagesConfigFileSettings(); + packagesConfig.Packages = new HashSet(); + using (var stringWriter = new StringWriter()) { - using (var xw = XmlWriter.Create(stringWriter, settings)) + using (var xw = XmlWriter.Create(stringWriter, xmlWriterSettings)) { - xw.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""); - xw.WriteStartElement("packages"); - - foreach (var packageResult in packageResults) + foreach (var packageResult in installedPackages) { - xw.WriteStartElement("package"); - xw.WriteAttributeString("id", packageResult.PackageMetadata.Id); + var packageElement = new PackagesConfigFilePackageSetting + { + Id = packageResult.PackageMetadata.Id + }; if (configuration.ExportCommand.IncludeVersionNumbers) { - xw.WriteAttributeString("version", packageResult.PackageMetadata.Version.ToString()); + packageElement.Version = packageResult.PackageMetadata.Version.ToString(); } - xw.WriteEndElement(); + if (configuration.ExportCommand.IncludeRememberedPackageArguments) + { + var pkgInfo = _packageInfoService.Get(packageResult.PackageMetadata); + configuration.Features.UseRememberedArgumentsForUpgrades = true; + var rememberedConfig = _nugetService.GetPackageConfigFromRememberedArguments(configuration, pkgInfo); + + // Mirrors the arguments captured in ChocolateyPackageService.CaptureArguments() + if (configuration.Prerelease) packageElement.Prerelease = true; + if (configuration.IgnoreDependencies) packageElement.IgnoreDependencies = true; + if (configuration.ForceX86) packageElement.ForceX86 = true; + if (!string.IsNullOrWhiteSpace(configuration.InstallArguments)) packageElement.InstallArguments = configuration.InstallArguments; + if (configuration.OverrideArguments) packageElement.OverrideArguments = true; + if (configuration.ApplyInstallArgumentsToDependencies) packageElement.ApplyInstallArgumentsToDependencies = true; + if (!string.IsNullOrWhiteSpace(configuration.PackageParameters)) packageElement.PackageParameters = configuration.PackageParameters; + if (configuration.ApplyPackageParametersToDependencies) packageElement.ApplyPackageParametersToDependencies = true; + if (configuration.AllowDowngrade) packageElement.AllowDowngrade = true; + if (!string.IsNullOrWhiteSpace(configuration.SourceCommand.Username)) packageElement.User = configuration.SourceCommand.Username; + if (!string.IsNullOrWhiteSpace(configuration.SourceCommand.Password)) packageElement.Password = configuration.SourceCommand.Password; + if (!string.IsNullOrWhiteSpace(configuration.SourceCommand.Certificate)) packageElement.Cert = configuration.SourceCommand.Certificate; + if (!string.IsNullOrWhiteSpace(configuration.SourceCommand.CertificatePassword)) packageElement.CertPassword = configuration.SourceCommand.CertificatePassword; + // Arguments from the global options set + if (configuration.CommandExecutionTimeoutSeconds != ApplicationParameters.DefaultWaitForExitInSeconds) + { + packageElement.ExecutionTimeout = configuration.CommandExecutionTimeoutSeconds; + } + // This was discussed in the PR, and because it is potentially system specific, it should not be included in the exported file + // if (!string.IsNullOrWhiteSpace(configuration.CacheLocation)) packageElement.CacheLocation = configuration.CacheLocation; + // if (configuration.Features.FailOnStandardError) packageElement.FailOnStderr = true; + // if (!configuration.Features.UsePowerShellHost) packageElement.UseSystemPowershell = true; + + if (!configuration.ExportCommand.ExcludePins && pkgInfo.IsPinned) + { + xw.WriteAttributeString("pinPackage", "true"); + } + + // Make sure to reset the configuration so as to be able to parse the next set of remembered arguments + configuration.RevertChanges(); + } + + packagesConfig.Packages.Add(packageElement); } - xw.WriteEndElement(); - xw.Flush(); + XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + + var packagesConfigSerializer = new XmlSerializer(typeof(PackagesConfigFileSettings)); + packagesConfigSerializer.Serialize(xw, packagesConfig, ns); } var fullOutputFilePath = _fileSystem.GetFullPath(configuration.ExportCommand.OutputFilePath); diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 9954353c49..999795c187 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -252,8 +252,10 @@ private void AppendOutput(StringBuilder propertyValues, string append) // configuration set variables public string CacheLocation { get; set; } + public bool CacheLocationArgumentWasPassed { get; set; } public int CommandExecutionTimeoutSeconds { get; set; } + public bool CommandExecutionTimeoutSecondsArgumentWasPassed { get; set; } public int WebRequestTimeoutSeconds { get; set; } public string DefaultTemplateName { get; set; } @@ -732,6 +734,8 @@ public sealed class ProxyConfiguration public sealed class ExportCommandConfiguration { public bool IncludeVersionNumbers { get; set; } + public bool IncludeRememberedPackageArguments { get; set; } + public bool ExcludePins { get; set; } public string OutputFilePath { get; set; } } diff --git a/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs index 2344a34a6a..61e4e07952 100644 --- a/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs +++ b/src/chocolatey/infrastructure.app/configuration/PackagesConfigFilePackageSetting.cs @@ -15,6 +15,7 @@ // limitations under the License. using System; +using System.ComponentModel; using System.Xml.Serialization; namespace chocolatey.infrastructure.app.configuration @@ -44,46 +45,130 @@ public sealed class PackagesConfigFilePackageSetting [XmlAttribute(AttributeName = "applyPackageParametersToDependencies")] public bool ApplyPackageParametersToDependencies { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ApplyPackageParametersToDependenciesSpecified + { + get { return ApplyPackageParametersToDependencies; } + } + [XmlAttribute(AttributeName = "applyInstallArgumentsToDependencies")] public bool ApplyInstallArgumentsToDependencies { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ApplyInstallArgumentsToDependenciesSpecified + { + get { return ApplyInstallArgumentsToDependencies; } + } + [XmlAttribute(AttributeName = "forceX86")] public bool ForceX86 { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ForceX86Specified + { + get { return ForceX86; } + } + [XmlAttribute(AttributeName = "ignoreDependencies")] public bool IgnoreDependencies { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool IgnoreDependenciesSpecified + { + get { return IgnoreDependencies; } + } + [XmlAttribute(AttributeName = "disabled")] public bool Disabled { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool DisabledSpecified + { + get { return Disabled; } + } + [XmlAttribute(AttributeName = "pinPackage")] public bool PinPackage { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool PinPackageSpecified + { + get { return PinPackage; } + } + [System.ComponentModel.DefaultValue(-1)] [XmlAttribute(AttributeName = "executionTimeout")] public int ExecutionTimeout { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ExecutionTimeoutSpecified + { + get { return ExecutionTimeout != 0; } + } + [XmlAttribute(AttributeName = "force")] public bool Force { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ForceSpecified + { + get { return Force; } + } + [XmlAttribute(AttributeName = "prerelease")] public bool Prerelease { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool PrereleaseSpecified + { + get { return Prerelease; } + } + [XmlAttribute(AttributeName = "overrideArguments")] public bool OverrideArguments { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool OverrideArgumentsSpecified + { + get { return OverrideArguments; } + } + [XmlAttribute(AttributeName = "notSilent")] public bool NotSilent { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool NotSilentSpecified + { + get { return NotSilent; } + } + [XmlAttribute(AttributeName = "allowDowngrade")] public bool AllowDowngrade { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool AllowDowngradeSpecified + { + get { return AllowDowngrade; } + } + [XmlAttribute(AttributeName = "forceDependencies")] public bool ForceDependencies { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ForceDependenciesSpecified + { + get { return ForceDependencies; } + } + [XmlAttribute(AttributeName = "skipAutomationScripts")] public bool SkipAutomationScripts { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool SkipAutomationScriptsSpecified + { + get { return SkipAutomationScripts; } + } + [XmlAttribute(AttributeName = "user")] public string User { get; set; } @@ -99,15 +184,39 @@ public sealed class PackagesConfigFilePackageSetting [XmlAttribute(AttributeName = "ignoreChecksums")] public bool IgnoreChecksums { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool IgnoreChecksumsSpecified + { + get { return IgnoreChecksums; } + } + [XmlAttribute(AttributeName = "allowEmptyChecksums")] public bool AllowEmptyChecksums { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool AllowEmptyChecksumsSpecified + { + get { return AllowEmptyChecksums; } + } + [XmlAttribute(AttributeName = "allowEmptyChecksumsSecure")] public bool AllowEmptyChecksumsSecure { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool AllowEmptyChecksumsSecureSpecified + { + get { return AllowEmptyChecksumsSecure; } + } + [XmlAttribute(AttributeName = "requireChecksums")] public bool RequireChecksums { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool RequireChecksumsSpecified + { + get { return RequireChecksums; } + } + [XmlAttribute(AttributeName = "downloadChecksum")] public string DownloadChecksum { get; set; } @@ -123,40 +232,112 @@ public sealed class PackagesConfigFilePackageSetting [XmlAttribute(AttributeName = "ignorePackageExitCodes")] public bool IgnorePackageExitCodes { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool IgnorePackageExitCodesSpecified + { + get { return IgnorePackageExitCodes; } + } + [XmlAttribute(AttributeName = "usePackageExitCodes")] public bool UsePackageExitCodes { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool UsePackageExitCodesSpecified + { + get { return UsePackageExitCodes; } + } + [XmlAttribute(AttributeName = "stopOnFirstFailure")] public bool StopOnFirstFailure { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool StopOnFirstFailureSpecified + { + get { return StopOnFirstFailure; } + } + [XmlAttribute(AttributeName = "exitWhenRebootDetected")] public bool ExitWhenRebootDetected { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ExitWhenRebootDetectedSpecified + { + get { return ExitWhenRebootDetected; } + } + [XmlAttribute(AttributeName = "ignoreDetectedReboot")] public bool IgnoreDetectedReboot { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool IgnoreDetectedRebootSpecified + { + get { return IgnoreDetectedReboot; } + } + [XmlAttribute(AttributeName = "disableRepositoryOptimizations")] public bool DisableRepositoryOptimizations { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool DisableRepositoryOptimizationsSpecified + { + get { return DisableRepositoryOptimizations; } + } + [XmlAttribute(AttributeName = "acceptLicense")] public bool AcceptLicense { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool AcceptLicenseSpecified + { + get { return AcceptLicense; } + } + [XmlAttribute(AttributeName = "confirm")] public bool Confirm { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool ConfirmSpecified + { + get { return Confirm; } + } + [XmlAttribute(AttributeName = "limitOutput")] public bool LimitOutput { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool LimitOutputSpecified + { + get { return LimitOutput; } + } + [XmlAttribute(AttributeName = "cacheLocation")] public string CacheLocation { get; set; } [XmlAttribute(AttributeName = "failOnStderr")] public bool FailOnStderr { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool FailOnStderrSpecified + { + get { return FailOnStderr; } + } + [XmlAttribute(AttributeName = "useSystemPowershell")] public bool UseSystemPowershell { get; set; } + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool UseSystemPowershellSpecified + { + get { return UseSystemPowershell; } + } + [XmlAttribute(AttributeName = "noProgress")] public bool NoProgress { get; set; } + + [XmlIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public bool NoProgressSpecified + { + get { return NoProgress; } + } } } diff --git a/src/chocolatey/infrastructure.app/services/INugetService.cs b/src/chocolatey/infrastructure.app/services/INugetService.cs index a7a3a30f35..9ea50e0078 100644 --- a/src/chocolatey/infrastructure.app/services/INugetService.cs +++ b/src/chocolatey/infrastructure.app/services/INugetService.cs @@ -18,6 +18,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using chocolatey.infrastructure.app.configuration; +using chocolatey.infrastructure.app.domain; using chocolatey.infrastructure.results; namespace chocolatey.infrastructure.app.services @@ -67,6 +68,16 @@ public interface INugetService : ISourceRunner /// The configuration IEnumerable GetInstalledPackages(ChocolateyConfiguration config); + + /// + /// Gets the configuration from remembered arguments + /// + /// The original configuration. + /// The package information. + /// The modified configuration, so it can be used + ChocolateyConfiguration GetPackageConfigFromRememberedArguments(ChocolateyConfiguration config, + ChocolateyPackageInformation packageInfo); + #pragma warning disable IDE0022, IDE1006 [Obsolete("This overload is deprecated and will be removed in v3.")] ConcurrentDictionary get_outdated(ChocolateyConfiguration config); diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 7e4251846e..6821d2a4b6 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -571,7 +571,7 @@ public virtual ConcurrentDictionary Install(ChocolateyCon if (packageNames.Count == 1) { var packageName = packageNames.DefaultIfEmpty(string.Empty).FirstOrDefault(); - if (packageName.EndsWith(NuGetConstants.PackageExtension) || packageName.EndsWith(PackagingConstants.ManifestExtension)) + if (packageName.EndsWith(NuGetConstants.PackageExtension, StringComparison.OrdinalIgnoreCase) || packageName.EndsWith(PackagingConstants.ManifestExtension, StringComparison.OrdinalIgnoreCase)) { this.Log().Warn(ChocolateyLoggers.Important, "DEPRECATION WARNING"); this.Log().Warn(InstallWithFilePathDeprecationMessage); @@ -581,7 +581,7 @@ public virtual ConcurrentDictionary Install(ChocolateyCon config.Sources = _fileSystem.GetDirectoryName(_fileSystem.GetFullPath(packageName)); - if (packageName.EndsWith(PackagingConstants.ManifestExtension)) + if (packageName.EndsWith(PackagingConstants.ManifestExtension, StringComparison.OrdinalIgnoreCase)) { packageNames.Add(_fileSystem.GetFilenameWithoutExtension(packageName)); @@ -631,7 +631,7 @@ public virtual ConcurrentDictionary Install(ChocolateyCon var installedPackage = allLocalPackages.FirstOrDefault(p => p.Name.IsEqualTo(packageName)); - if (Platform.GetPlatform() != PlatformType.Windows && !packageName.EndsWith(".template")) + if (Platform.GetPlatform() != PlatformType.Windows && !packageName.EndsWith(".template", StringComparison.OrdinalIgnoreCase)) { var logMessage = "{0} is not a supported package on non-Windows systems.{1}Only template packages are currently supported.".FormatWith(packageName, Environment.NewLine); this.Log().Warn(ChocolateyLoggers.Important, logMessage); @@ -1216,7 +1216,7 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon continue; } - SetConfigFromRememberedArguments(config, pkgInfo); + config = GetPackageConfigFromRememberedArguments(config, pkgInfo); var pathResolver = NugetCommon.GetPathResolver(_fileSystem); var nugetProject = new FolderNuGetProject(ApplicationParameters.PackagesLocation, pathResolver, NuGetFramework.AnyFramework); @@ -1354,12 +1354,12 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon var logMessage = "{0} is pinned. Skipping pinned package.".FormatWith(packageName); packageResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); - + if (config.RegularOutput) { this.Log().Warn(ChocolateyLoggers.Important, logMessage); } - + continue; } else @@ -1371,7 +1371,7 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon { this.Log().Warn(ChocolateyLoggers.Important, logMessage); } - + config.PinPackage = true; } } @@ -1453,7 +1453,7 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon } var removedSources = new HashSet(); - + if (!config.UpgradeCommand.IgnorePinned) { removedSources.AddRange(RemovePinnedSourceDependencies(sourcePackageDependencyInfos, allLocalPackages)); @@ -1937,12 +1937,7 @@ public virtual ConcurrentDictionary GetOutdated(Chocolate return outdatedPackages; } - /// - /// Sets the configuration for the package upgrade - /// - /// The configuration. - /// The package information. - /// The original unmodified configuration, so it can be reset after upgrade + [Obsolete("This method is deprecated and will be removed in v3.")] protected virtual ChocolateyConfiguration SetConfigFromRememberedArguments(ChocolateyConfiguration config, ChocolateyPackageInformation packageInfo) { if (!config.Features.UseRememberedArgumentsForUpgrades || string.IsNullOrWhiteSpace(packageInfo.Arguments)) @@ -1968,6 +1963,16 @@ protected virtual ChocolateyConfiguration SetConfigFromRememberedArguments(Choco ConfigurationOptions.OptionSet.Parse(packageArguments); // there may be overrides from the user running upgrade + if (!string.IsNullOrWhiteSpace(originalConfig.PackageParameters)) + { + config.PackageParameters = originalConfig.PackageParameters; + } + + if (!string.IsNullOrWhiteSpace(originalConfig.InstallArguments)) + { + config.InstallArguments = originalConfig.InstallArguments; + } + if (!string.IsNullOrWhiteSpace(originalConfig.SourceCommand.Username)) { config.SourceCommand.Username = originalConfig.SourceCommand.Username; @@ -1988,9 +1993,174 @@ protected virtual ChocolateyConfiguration SetConfigFromRememberedArguments(Choco config.SourceCommand.CertificatePassword = originalConfig.SourceCommand.CertificatePassword; } + if (originalConfig.CacheLocationArgumentWasPassed && !string.IsNullOrWhiteSpace(originalConfig.CacheLocation)) + { + config.CacheLocation = originalConfig.CacheLocation; + } + + if (originalConfig.CommandExecutionTimeoutSecondsArgumentWasPassed) + { + config.CommandExecutionTimeoutSeconds = originalConfig.CommandExecutionTimeoutSeconds; + } + return originalConfig; } + /// + /// Gets the configuration from remembered arguments + /// + /// The original configuration. + /// The package information. + /// The modified configuration, so it can be used + public virtual ChocolateyConfiguration GetPackageConfigFromRememberedArguments(ChocolateyConfiguration config, ChocolateyPackageInformation packageInfo) + { + if (!config.Features.UseRememberedArgumentsForUpgrades || string.IsNullOrWhiteSpace(packageInfo.Arguments)) + { + return config; + } + + var packageArgumentsUnencrypted = packageInfo.Arguments.ContainsSafe(" --") && packageInfo.Arguments.ToStringSafe().Length > 4 ? packageInfo.Arguments : NugetEncryptionUtility.DecryptString(packageInfo.Arguments); + + var sensitiveArgs = true; + if (!ArgumentsUtility.SensitiveArgumentsProvided(packageArgumentsUnencrypted)) + { + sensitiveArgs = false; + this.Log().Debug(ChocolateyLoggers.Verbose, "{0} - Adding remembered arguments: {1}".FormatWith(packageInfo.Package.Id, packageArgumentsUnencrypted.EscapeCurlyBraces())); + } + + var packageArgumentsSplit = packageArgumentsUnencrypted.Split(new[] { " --" }, StringSplitOptions.RemoveEmptyEntries); + var packageArguments = new List(); + foreach (var packageArgument in packageArgumentsSplit.OrEmpty()) + { + var packageArgumentSplit = packageArgument.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries); + var optionName = packageArgumentSplit[0].ToStringSafe(); + var optionValue = string.Empty; + if (packageArgumentSplit.Length == 2) + { + optionValue = packageArgumentSplit[1].ToStringSafe().UnquoteSafe(); + if (optionValue.StartsWith("'")) + { + optionValue.UnquoteSafe(); + } + } + + if (sensitiveArgs) + { + this.Log().Debug(ChocolateyLoggers.Verbose, "{0} - Adding '{1}' to arguments. Values not shown due to detected sensitive arguments".FormatWith(packageInfo.Package.Id, optionName.EscapeCurlyBraces())); + } + packageArguments.Add("--{0}{1}".FormatWith(optionName, string.IsNullOrWhiteSpace(optionValue) ? string.Empty : "=" + optionValue)); + } + + var originalConfig = config.DeepCopy(); + var rememberedOptionSet = new OptionSet(); + + rememberedOptionSet + .Add("pre|prerelease", + "Prerelease - Include Prereleases? Defaults to false.", + option => config.Prerelease = option != null) + .Add("i|ignoredependencies|ignore-dependencies", + "IgnoreDependencies - Ignore dependencies when installing package(s). Defaults to false.", + option => config.IgnoreDependencies = option != null) + .Add("x86|forcex86", + "ForceX86 - Force x86 (32bit) installation on 64 bit systems. Defaults to false.", + option => config.ForceX86 = option != null) + .Add("ia=|installargs=|install-args=|installarguments=|install-arguments=", + "InstallArguments - Install Arguments to pass to the native installer in the package. Defaults to unspecified.", + option => config.InstallArguments = option.UnquoteSafe()) + .Add("o|override|overrideargs|overridearguments|override-arguments", + "OverrideArguments - Should install arguments be used exclusively without appending to current package passed arguments? Defaults to false.", + option => config.OverrideArguments = option != null) + .Add("argsglobal|args-global|installargsglobal|install-args-global|applyargstodependencies|apply-args-to-dependencies|apply-install-arguments-to-dependencies", + "Apply Install Arguments To Dependencies - Should install arguments be applied to dependent packages? Defaults to false.", + option => config.ApplyInstallArgumentsToDependencies = option != null) + .Add("params=|parameters=|pkgparameters=|packageparameters=|package-parameters=", + "PackageParameters - Parameters to pass to the package. Defaults to unspecified.", + option => config.PackageParameters = option.UnquoteSafe()) + .Add("paramsglobal|params-global|packageparametersglobal|package-parameters-global|applyparamstodependencies|apply-params-to-dependencies|apply-package-parameters-to-dependencies", + "Apply Package Parameters To Dependencies - Should package parameters be applied to dependent packages? Defaults to false.", + option => config.ApplyPackageParametersToDependencies = option != null) + .Add("allowdowngrade|allow-downgrade", + "AllowDowngrade - Should an attempt at downgrading be allowed? Defaults to false.", + option => config.AllowDowngrade = option != null) + .Add("u=|user=", + "User - used with authenticated feeds. Defaults to empty.", + option => config.SourceCommand.Username = option.UnquoteSafe()) + .Add("p=|password=", + "Password - the user's password to the source. Defaults to empty.", + option => config.SourceCommand.Password = option.UnquoteSafe()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => config.SourceCommand.Certificate = option.UnquoteSafe()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => config.SourceCommand.CertificatePassword = option.UnquoteSafe()) + .Add("timeout=|execution-timeout=", + "CommandExecutionTimeout (in seconds) - The time to allow a command to finish before timing out. Overrides the default execution timeout in the configuration of {0} seconds. '0' for infinite starting in 0.10.4.".FormatWith(config.CommandExecutionTimeoutSeconds.ToString()), + option => + { + var timeout = 0; + var timeoutString = option.UnquoteSafe(); + int.TryParse(timeoutString, out timeout); + if (timeout > 0 || timeoutString.IsEqualTo("0")) + { + config.CommandExecutionTimeoutSeconds = timeout; + } + }) + .Add("c=|cache=|cachelocation=|cache-location=", + "CacheLocation - Location for download cache, defaults to %TEMP% or value in chocolatey.config file.", + option => config.CacheLocation = option.UnquoteSafe()) + .Add("use-system-powershell", + "UseSystemPowerShell - Execute PowerShell using an external process instead of the built-in PowerShell host. Should only be used when internal host is failing. Available in 0.9.10+.", + option => config.Features.UsePowerShellHost = option != null); + + rememberedOptionSet.Parse(packageArguments); + + // there may be overrides from the user running upgrade + if (!string.IsNullOrWhiteSpace(originalConfig.PackageParameters)) + { + config.PackageParameters = originalConfig.PackageParameters; + } + + if (!string.IsNullOrWhiteSpace(originalConfig.InstallArguments)) + { + config.InstallArguments = originalConfig.InstallArguments; + } + + if (!string.IsNullOrWhiteSpace(originalConfig.SourceCommand.Username)) + { + config.SourceCommand.Username = originalConfig.SourceCommand.Username; + } + + if (!string.IsNullOrWhiteSpace(originalConfig.SourceCommand.Password)) + { + config.SourceCommand.Password = originalConfig.SourceCommand.Password; + } + + if (!string.IsNullOrWhiteSpace(originalConfig.SourceCommand.Certificate)) + { + config.SourceCommand.Certificate = originalConfig.SourceCommand.Certificate; + } + + if (!string.IsNullOrWhiteSpace(originalConfig.SourceCommand.CertificatePassword)) + { + config.SourceCommand.CertificatePassword = originalConfig.SourceCommand.CertificatePassword; + } + + if (originalConfig.CacheLocationArgumentWasPassed && !string.IsNullOrWhiteSpace(originalConfig.CacheLocation)) + { + config.CacheLocation = originalConfig.CacheLocation; + } + + if (originalConfig.CommandExecutionTimeoutSecondsArgumentWasPassed) + { + config.CommandExecutionTimeoutSeconds = originalConfig.CommandExecutionTimeoutSeconds; + } + + // We can't override switches because we don't know here if they were set on the command line + + return config; + } + private bool HasMissingDependency(PackageResult package, List allLocalPackages) { foreach (var dependency in package.PackageMetadata.DependencyGroups.SelectMany(d => d.Packages))