From a0df909be17c83a050df0a0dadf0eccb71e2176e Mon Sep 17 00:00:00 2001 From: Gary Ewan Park Date: Mon, 13 May 2019 08:00:08 +0100 Subject: [PATCH] (GH-357) Add Export Command This will allow the creation of a packages.config file of all the currently installed packages on the machine. Usage of this command will be: choco export -o c:/temp/packages.config --include-version-numbers This command is particularly useful when re-building a machine. i.e. First export all packages currently installed on machine, and then replay this packages.config via choco install packages.config on new machine. --- src/chocolatey/chocolatey.csproj | 1 + .../commands/ChocolateyExportCommand.cs | 185 ++++++++++++++++++ .../configuration/ChocolateyConfiguration.cs | 16 ++ .../registration/ContainerBinding.cs | 3 +- 4 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index f1d4e6bc7e..6706e941b2 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -113,6 +113,7 @@ Properties\SolutionVersion.cs + diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs new file mode 100644 index 0000000000..726a00c8f8 --- /dev/null +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyExportCommand.cs @@ -0,0 +1,185 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.commands +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; + using System.Xml; + using attributes; + using commandline; + using configuration; + using filesystem; + using infrastructure.commands; + using logging; + using services; + using tolerance; + + [CommandFor("export", "exports list of currently installed packages")] + public class ChocolateyExportCommand : ICommand + { + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + + public ChocolateyExportCommand(INugetService nugetService, IFileSystem fileSystem) + { + _nugetService = nugetService; + _fileSystem = fileSystem; + } + + public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) + { + optionSet + .Add("o=|output-file-path=", + "Output File Path - the path to where the list of currently installed packages should be saved. Defaults to packages.config.", + option => configuration.ExportCommand.OutputFilePath = option.remove_surrounding_quotes()) + .Add("include-version-numbers", + "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) + ; + } + + public void handle_additional_argument_parsing(IList unparsedArguments, ChocolateyConfiguration configuration) + { + if (unparsedArguments.Count > 0) + { + throw new ApplicationException("Please see the help menu for the export command"); + } + } + + public void handle_validation(ChocolateyConfiguration configuration) + { + // Currently, no additional validation is required. + } + + public void help_message(ChocolateyConfiguration configuration) + { + this.Log().Info(ChocolateyLoggers.Important, "Export Command"); + this.Log().Info(@" +Export all currently installed packages to a file. + +This is especially helpful when re-building a machine that was created +using Chocolatey. Export all packages to a file, and then re-install +those packages onto new machine using `choco install packages.config`. +"); + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); + "chocolatey".Log().Info(@" + choco export [] +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Examples"); + "chocolatey".Log().Info(@" + choco export + choco export --include-version-numbers + choco export --output-file-path=""c:\temp\packages.config"" + choco export --output-file-path=""c:\temp\packages.config"" --include-version-numbers + +NOTE: See scripting in the command reference (`choco -?`) for how to + write proper scripts and integrations. + +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Exit Codes"); + "chocolatey".Log().Info(@" +Exit codes that normally result from running this command. + +Normal: + - 0: operation was successful, no issues detected + - -1 or 1: an error has occurred + +If you find other exit codes that we have not yet documented, please + file a ticket so we can document it at + https://github.com/chocolatey/choco/issues/new/choose. + +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Options and Switches"); + } + + public bool may_require_admin_access() + { + return false; + } + + public void noop(ChocolateyConfiguration configuration) + { + this.Log().Info("Export would have been with options: {0} Output File Path={1}{0} Include Version Numbers:{2}".format_with(Environment.NewLine, configuration.ExportCommand.OutputFilePath, configuration.ExportCommand.IncludeVersionNumbers)); + } + + public void run(ChocolateyConfiguration configuration) + { + var packageResults = _nugetService.get_all_installed_packages(configuration); + var settings = new XmlWriterSettings { Indent = true, Encoding = new UTF8Encoding(false) }; + + FaultTolerance.try_catch_with_logging_exception( + () => + { + using (var stringWriter = new StringWriter()) + { + using (var xw = XmlWriter.Create(stringWriter, settings)) + { + xw.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""); + xw.WriteStartElement("packages"); + + foreach (var packageResult in packageResults) + { + xw.WriteStartElement("package"); + xw.WriteAttributeString("id", packageResult.Package.Id); + + if (configuration.ExportCommand.IncludeVersionNumbers) + { + xw.WriteAttributeString("version", packageResult.Package.Version.ToString()); + } + + xw.WriteEndElement(); + } + + xw.WriteEndElement(); + xw.Flush(); + } + + var fileExists = _fileSystem.file_exists(configuration.ExportCommand.OutputFilePath); + + // If the file doesn't already exist, just write the new one out directly + if (!fileExists) + { + _fileSystem.write_file( + configuration.ExportCommand.OutputFilePath, + stringWriter.GetStringBuilder().ToString(), + new UTF8Encoding(false)); + + return; + } + + + // Otherwise, create an update file, and resiliently move it into place. + var tempUpdateFile = configuration.ExportCommand.OutputFilePath + "." + Process.GetCurrentProcess().Id + ".update"; + _fileSystem.write_file(tempUpdateFile, + stringWriter.GetStringBuilder().ToString(), + new UTF8Encoding(false)); + + _fileSystem.replace_file(tempUpdateFile, configuration.ExportCommand.OutputFilePath, configuration.ExportCommand.OutputFilePath + ".backup"); + } + }, + errorMessage: "Error exporting currently installed packages", + throwError: true + ); + } + } +} diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index c5cc33f73f..52bf9c74d7 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -50,6 +50,7 @@ public ChocolateyConfiguration() PinCommand = new PinCommandConfiguration(); OutdatedCommand = new OutdatedCommandConfiguration(); Proxy = new ProxyConfiguration(); + ExportCommand = new ExportCommandConfiguration(); #if DEBUG AllowUnofficialBuild = true; #endif @@ -334,6 +335,8 @@ private void append_output(StringBuilder propertyValues, string append) /// public OutdatedCommandConfiguration OutdatedCommand { get; set; } + public ExportCommandConfiguration ExportCommand { get; set; } + /// /// Configuration related specifically to proxies. /// @@ -545,4 +548,17 @@ public sealed class ProxyConfiguration public string BypassList { get; set; } public bool BypassOnLocal { get; set; } } + + [Serializable] + public sealed class ExportCommandConfiguration + { + public ExportCommandConfiguration() + { + OutputFilePath = "packages.config"; + } + + public bool IncludeVersionNumbers { get; set; } + + public string OutputFilePath { get; set; } + } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 67ffc551b5..05d57c159f 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -95,7 +95,8 @@ public void RegisterComponents(Container container) new ChocolateyApiKeyCommand(container.GetInstance()), new ChocolateyUnpackSelfCommand(container.GetInstance()), new ChocolateyVersionCommand(container.GetInstance()), - new ChocolateyUpdateCommand(container.GetInstance()) + new ChocolateyUpdateCommand(container.GetInstance()), + new ChocolateyExportCommand(container.GetInstance(), container.GetInstance()) }; return list.AsReadOnly(); }, Lifestyle.Singleton);