From cf2f6650db2c06b4bf60a057fa88de9d06a5d6af Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:32:12 -0500 Subject: [PATCH 01/29] (GH-14) Define ISourceRunner --- src/chocolatey/chocolatey.csproj | 3 +- .../{SpecialSourceType.cs => SourceType.cs} | 2 +- .../services/ChocolateyPackageService.cs | 4 +- .../services/ISourceRunner.cs | 100 ++++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) rename src/chocolatey/infrastructure.app/domain/{SpecialSourceType.cs => SourceType.cs} (93%) create mode 100644 src/chocolatey/infrastructure.app/services/ISourceRunner.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 99d7158316..df979ddb99 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -109,6 +109,7 @@ + @@ -183,7 +184,7 @@ - + diff --git a/src/chocolatey/infrastructure.app/domain/SpecialSourceType.cs b/src/chocolatey/infrastructure.app/domain/SourceType.cs similarity index 93% rename from src/chocolatey/infrastructure.app/domain/SpecialSourceType.cs rename to src/chocolatey/infrastructure.app/domain/SourceType.cs index 2ae07f5e69..6c193ef063 100644 --- a/src/chocolatey/infrastructure.app/domain/SpecialSourceType.cs +++ b/src/chocolatey/infrastructure.app/domain/SourceType.cs @@ -18,7 +18,7 @@ namespace chocolatey.infrastructure.app.domain /// /// Special source modifiers that use alternate sources for packages /// - public enum SpecialSourceType + public enum SourceType { //this is what it should be when it's not set normal, diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 2cddc469d6..77f7b4e04d 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -59,7 +59,7 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p public void list_noop(ChocolateyConfiguration config) { - if (config.Sources.is_equal_to(SpecialSourceType.webpi.to_string())) + if (config.Sources.is_equal_to(SourceType.webpi.to_string())) { //todo: webpi } @@ -73,7 +73,7 @@ public void list_run(ChocolateyConfiguration config, bool logResults) { this.Log().Debug(() => "Searching for package information"); - if (config.Sources.is_equal_to(SpecialSourceType.webpi.to_string())) + if (config.Sources.is_equal_to(SourceType.webpi.to_string())) { //todo: webpi //install webpi if not installed diff --git a/src/chocolatey/infrastructure.app/services/ISourceRunner.cs b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs new file mode 100644 index 0000000000..5a8eedd95b --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs @@ -0,0 +1,100 @@ +// Copyright © 2011 - Present 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.services +{ + using System; + using System.Collections.Concurrent; + using configuration; + using domain; + using results; + + public interface ISourceRunner + { + /// + /// Gets the source type the source runner implements + /// + /// + /// The type of the source. + /// + SourceType SourceType { get; } + + /// + /// Ensures the application that controls a source is installed + /// + /// The configuration. + /// The action to continue with as part of the install + void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction); + + /// + /// Run list in noop mode + /// + /// The configuration. + void list_noop(ChocolateyConfiguration config); + + /// + /// Lists/searches for packages from the source feed + /// + /// The configuration. + /// Should results be logged? + /// + ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults); + + /// + /// Run install in noop mode + /// + /// The configuration. + /// The action to continue with for each noop test install. + void install_noop(ChocolateyConfiguration config, Action continueAction); + + /// + /// Installs packages from the source feed + /// + /// The configuration. + /// The action to continue with when install is successful. + /// results of installs + ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction); + + /// + /// Run upgrade in noop mode + /// + /// The configuration. + /// The action to continue with for each noop test upgrade. + ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction); + + /// + /// Upgrades packages from NuGet related feeds + /// + /// The configuration. + /// The action to continue with when upgrade is successful. + /// results of installs + ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction); + + /// + /// Run uninstall in noop mode + /// + /// The configuration. + /// The action to continue with for each noop test upgrade. + void uninstall_noop(ChocolateyConfiguration config, Action continueAction); + + /// + /// Uninstalls packages from NuGet related feeds + /// + /// The configuration. + /// The action to continue with when upgrade is successful. + /// results of installs + ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction); + } +} \ No newline at end of file From 83c4b62a2c0da82a0bb2c127e9ed10832e107463 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:34:19 -0500 Subject: [PATCH 02/29] (GH-14) Add SourceType to config Set the value for source type based what is passed in, default to normal. --- .../configuration/ChocolateyConfiguration.cs | 2 ++ .../infrastructure.app/runners/GenericRunner.cs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index b92b9bd8c5..46445dd0ea 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -33,6 +33,7 @@ public ChocolateyConfiguration() { RegularOutput = true; PromptForConfirmation = true; + SourceType = SourceType.normal; Information = new InformationCommandConfiguration(); Features = new FeaturesConfiguration(); NewCommand = new NewCommandConfiguration(); @@ -143,6 +144,7 @@ private void append_output(StringBuilder propertyValues, string append) /// One or more source locations set by configuration or by command line. Separated by semi-colon /// public string Sources { get; set; } + public SourceType SourceType { get; set; } // top level commands diff --git a/src/chocolatey/infrastructure.app/runners/GenericRunner.cs b/src/chocolatey/infrastructure.app/runners/GenericRunner.cs index a19b845135..6ba73330ec 100644 --- a/src/chocolatey/infrastructure.app/runners/GenericRunner.cs +++ b/src/chocolatey/infrastructure.app/runners/GenericRunner.cs @@ -24,6 +24,7 @@ namespace chocolatey.infrastructure.app.runners using attributes; using commandline; using configuration; + using domain; using infrastructure.commands; using logging; using Console = System.Console; @@ -62,6 +63,8 @@ private ICommand find_command(ChocolateyConfiguration config, Container containe parseArgs.Invoke(command); } + set_source_type(config); + this.Log().Debug(() => "Configuration: {0}".format_with(config.ToString())); @@ -110,6 +113,13 @@ now be in a bad state. Only official builds are to be trusted. return command; } + private static void set_source_type(ChocolateyConfiguration config) + { + var sourceType = SourceType.normal; + Enum.TryParse(config.Sources, true, out sourceType); + config.SourceType = sourceType; + } + public void run(ChocolateyConfiguration config, Container container, bool isConsole, Action parseArgs) { var command = find_command(config, container, isConsole, parseArgs); From 0e9b3761532cad7d0df34337839d8053278bdf64 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:36:54 -0500 Subject: [PATCH 03/29] (refactor) CommandExecutor instance method Move remaining static `exec` method to instance method, recreate the static with the name `execute_static`. --- .../commands/CommandExecutorSpecs.cs | 2 +- .../commands/CommandExecutor.cs | 21 ++++++++++++++++++- .../commands/ICommandExecutor.cs | 10 +++++++++ .../commands/PowershellExecutor.cs | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs b/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs index 80b2868545..b375cab433 100644 --- a/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs @@ -47,7 +47,7 @@ public override void Context() public override void Because() { - result = CommandExecutor.execute("cmd.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, fileSystem.get_current_directory(), null, (s, e) => { errorOutput += e.Data; }, updateProcessPath: false); + result = commandExecutor.execute("cmd.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, fileSystem.get_current_directory(), null, (s, e) => { errorOutput += e.Data; }, updateProcessPath: false); } [Fact] diff --git a/src/chocolatey/infrastructure/commands/CommandExecutor.cs b/src/chocolatey/infrastructure/commands/CommandExecutor.cs index 409850d7b8..56a5cce761 100644 --- a/src/chocolatey/infrastructure/commands/CommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/CommandExecutor.cs @@ -76,7 +76,26 @@ public int execute(string process, string arguments, int waitForExitInSeconds, s return execute(process, arguments, waitForExitInSeconds, workingDirectory, null, null, updateProcessPath: true); } - public static int execute(string process, + public int execute(string process, + string arguments, + int waitForExitInSeconds, + string workingDirectory, + Action stdOutAction, + Action stdErrAction, + bool updateProcessPath + ) + { + return execute_static(process, + arguments, + waitForExitInSeconds, + file_system.get_directory_name(Assembly.GetExecutingAssembly().Location), + stdOutAction, + stdErrAction, + updateProcessPath + ); + } + + public static int execute_static(string process, string arguments, int waitForExitInSeconds, string workingDirectory, diff --git a/src/chocolatey/infrastructure/commands/ICommandExecutor.cs b/src/chocolatey/infrastructure/commands/ICommandExecutor.cs index 5e8a89dfa2..8d5226598b 100644 --- a/src/chocolatey/infrastructure/commands/ICommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/ICommandExecutor.cs @@ -30,6 +30,16 @@ int execute( Action stdOutAction, Action stdErrAction, bool updateProcessPath + ); + + int execute( + string process, + string arguments, + int waitForExitInSeconds, + string workingDirectory, + Action stdOutAction, + Action stdErrAction, + bool updateProcessPath ); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure/commands/PowershellExecutor.cs b/src/chocolatey/infrastructure/commands/PowershellExecutor.cs index 1092a87e6a..1a630310aa 100644 --- a/src/chocolatey/infrastructure/commands/PowershellExecutor.cs +++ b/src/chocolatey/infrastructure/commands/PowershellExecutor.cs @@ -46,7 +46,7 @@ Action stdErrAction //-NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%DIR%chocolatey.ps1' %PS_ARGS%" string arguments = "-NoProfile -NoLogo -ExecutionPolicy Bypass -Command \"{0}\"".format_with(command); - return CommandExecutor.execute( + return CommandExecutor.execute_static( _powershell, arguments, waitForExitSeconds, From 6af746f5a7c55656e754238a06fe07687217bba1 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:37:34 -0500 Subject: [PATCH 04/29] (maint) removing commented code --- src/chocolatey/infrastructure.app/services/RegistryService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index e5ba45371f..812a751e83 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -206,8 +206,6 @@ public bool value_exists(string keyPath, string value) { //todo: make this check less crazy... return get_installer_keys().RegistryKeys.Any(k => k.KeyPath == keyPath); - - //return Microsoft.Win32.Registry.GetValue(keyPath, value, null) != null; } public Registry read_from_file(string filePath) From 5311ce02aa3b28d9692773f1368f234af8047530 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:40:50 -0500 Subject: [PATCH 05/29] (GH-14) INugetService implements ISourceRunner --- .../services/INugetService.cs | 67 +------------------ .../services/NugetService.cs | 10 +++ 2 files changed, 12 insertions(+), 65 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/INugetService.cs b/src/chocolatey/infrastructure.app/services/INugetService.cs index 63d8ac92bb..4a4eb79905 100644 --- a/src/chocolatey/infrastructure.app/services/INugetService.cs +++ b/src/chocolatey/infrastructure.app/services/INugetService.cs @@ -15,27 +15,10 @@ namespace chocolatey.infrastructure.app.services { - using System; - using System.Collections.Concurrent; using configuration; - using results; - public interface INugetService + public interface INugetService : ISourceRunner { - /// - /// Run list in noop mode - /// - /// The configuration. - void list_noop(ChocolateyConfiguration config); - - /// - /// Lists/searches for package against nuget related feeds. - /// - /// The configuration. - /// Should results be logged? - /// - ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults); - /// /// Run pack in noop mode. /// @@ -61,53 +44,7 @@ public interface INugetService void push_run(ChocolateyConfiguration config); /// - /// Run install in noop mode - /// - /// The configuration. - /// The action to continue with for each noop test install. - void install_noop(ChocolateyConfiguration config, Action continueAction); - - /// - /// Installs packages from NuGet related feeds - /// - /// The configuration. - /// The action to continue with when install is successful. - /// results of installs - ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction); - - /// - /// Run upgrade in noop mode - /// - /// The configuration. - /// The action to continue with for each noop test upgrade. - /// what would have upgraded - ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction); - - /// - /// Upgrades packages from NuGet related feeds - /// - /// The configuration. - /// The action to continue with when upgrade is successful. - /// results of upgrades - ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction); - - /// - /// Run uninstall in noop mode - /// - /// The configuration. - /// The action to continue with for each noop test upgrade. - void uninstall_noop(ChocolateyConfiguration config, Action continueAction); - - /// - /// Uninstalls packages from NuGet related feeds - /// - /// The configuration. - /// The action to continue with when upgrade is successful. - /// results of uninstalls - ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction); - - /// - /// Remove the rollback directory for a package if it exists + /// Remove the rollback directory for a package if it exists /// /// Name of the package. void remove_rollback_directory_if_exists(string packageName); diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 6ce9fa32a4..8a4477c4c8 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -65,6 +65,16 @@ public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPack _filesService = filesService; } + public SourceType SourceType + { + get { return SourceType.normal; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + // nothing to do. Nuget.Core is already part of Chocolatey + } + public void list_noop(ChocolateyConfiguration config) { this.Log().Info("{0} would have searched for '{1}' against the following source(s) :\"{2}\"".format_with( From 0f125569cbbc83742c0ac8da514fbb94a4af0ba1 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:48:05 -0500 Subject: [PATCH 06/29] (GH-14) Web Platform Installer source This implements the WebPI alternate source. WebPI can install itself, list and install applications from WebPI. It does not implement the ability to upgrade or uninstall applications that it installs. --- .../ApplicationParameters.cs | 23 +- .../services/WebPiService.cs | 224 +++++++++++++----- 2 files changed, 179 insertions(+), 68 deletions(-) diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 86e9a0ab38..c6d94a64f8 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -54,6 +54,7 @@ public static class ApplicationParameters public static readonly string ChocolateyPackageInfoStoreLocation = _fileSystem.combine_paths(InstallLocation, ".chocolatey"); public static readonly string ExtensionsLocation = _fileSystem.combine_paths(InstallLocation, "extensions"); public static readonly string ChocolateyCommunityFeedPushSource = "https://chocolatey.org/"; + public static readonly string ChocolateyCommunityFeedSource = "https://chocolatey.org/api/v2/"; public static readonly string UserAgent = "Chocolatey Command Line"; public static readonly string RegistryValueInstallLocation = "InstallLocation"; public static readonly string AllPackages = "all"; @@ -86,19 +87,23 @@ public static class Messages public static readonly string NugetEventActionHeader = "Nuget called an event"; } + public static class SourceRunner + { + public static readonly string WebPiName = "Web Platform Installer"; + public static readonly string WebPiPackage = "webpicmd"; + public static readonly string WebPiExe = "webpicmd.exe"; + } + public static class OutputParser { - //todo: This becomes the WebPI parsing stuff instead - public static class Nuget + public static class WebPi { public const string PACKAGE_NAME_GROUP = "PkgName"; - public const string PACKAGE_VERSION_GROUP = "PkgVersion"; - public static readonly Regex AlreadyInstalled = new Regex(@"already installed", RegexOptions.Compiled); - public static readonly Regex NotInstalled = new Regex(@"not installed", RegexOptions.Compiled); - public static readonly Regex Installing = new Regex(@"Installing", RegexOptions.Compiled); - public static readonly Regex ResolvingDependency = new Regex(@"Attempting to resolve dependency", RegexOptions.Compiled); - public static readonly Regex PackageName = new Regex(@"'(?<{0}>[.\S]+)\s?".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); - public static readonly Regex PackageVersion = new Regex(@"(?<{0}>[\d\.]+[\-\w]*)[[)]?'".format_with(PACKAGE_VERSION_GROUP), RegexOptions.Compiled); + public static readonly Regex Installing =new Regex(@"Started installing:", RegexOptions.Compiled); + public static readonly Regex Installed = new Regex(@"Install completed \(Success\):", RegexOptions.Compiled); + public static readonly Regex AlreadyInstalled = new Regex(@"No products to be installed \(either not available or already installed\)", RegexOptions.Compiled); + //public static readonly Regex NotInstalled = new Regex(@"not installed", RegexOptions.Compiled); + public static readonly Regex PackageName = new Regex(@"'(?<{0}>[^']*)'".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); } } diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 25ada7c261..88e9b6f6ea 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -20,26 +20,25 @@ namespace chocolatey.infrastructure.app.services using System.Collections.Generic; using System.Text.RegularExpressions; using configuration; + using domain; using infrastructure.commands; using logging; using results; - public interface IWebPiService - { - } - - //todo this is the old nuget.exe installer code that needs cleaned up for webpi - public class WebPiService : IWebPiService + public sealed class WebPiService : ISourceRunner { private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; - private readonly string _webPiExePath = "webpicmd"; //ApplicationParameters.Tools.NugetExe; - private readonly IDictionary _webPiListArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - private readonly IDictionary _webPiInstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly string _exePath = ApplicationParameters.SourceRunner.WebPiExe; + private readonly string _appName = ApplicationParameters.SourceRunner.WebPiName; + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - public WebPiService(ICommandExecutor commandExecutor) + public WebPiService(ICommandExecutor commandExecutor, INugetService nugetService) { _commandExecutor = commandExecutor; + _nugetService = nugetService; set_cmd_args_dictionaries(); } @@ -48,93 +47,179 @@ public WebPiService(ICommandExecutor commandExecutor) /// private void set_cmd_args_dictionaries() { - //set_webpi_list_dictionary(); - set_webpi_install_dictionary(); + set_webpi_list_dictionary(_listArguments); + set_webpi_install_dictionary(_installArguments); + } + + /// + /// Sets webpicmd list dictionary + /// + private void set_webpi_list_dictionary(IDictionary args) + { + args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/List", Required = true}); + args.Add("_list_option_", new ExternalCommandArgument {ArgumentOption = "/ListOption:All", Required = true}); } /// /// Sets webpicmd install dictionary /// - private void set_webpi_install_dictionary() + private void set_webpi_install_dictionary(IDictionary args) { - _webPiInstallArguments.Add("_install_", new ExternalCommandArgument {ArgumentOption = "install", Required = true}); - _webPiInstallArguments.Add("_package_name_", new ExternalCommandArgument {ArgumentOption = PACKAGE_NAME_TOKEN, Required = true}); - _webPiInstallArguments.Add("Version", new ExternalCommandArgument {ArgumentOption = "-version ",}); - _webPiInstallArguments.Add("_output_directory_", new ExternalCommandArgument + args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/Install", Required = true}); + args.Add("_accept_eula_", new ExternalCommandArgument {ArgumentOption = "/AcceptEula", Required = true}); + args.Add("_suppress_reboot_", new ExternalCommandArgument {ArgumentOption = "/SuppressReboot", Required = true}); + args.Add("_package_name_", new ExternalCommandArgument { - ArgumentOption = "-outputdirectory ", - ArgumentValue = "{0}".format_with(ApplicationParameters.PackagesLocation), - QuoteValue = true, + ArgumentOption = "/Products:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, Required = true }); - _webPiInstallArguments.Add("Sources", new ExternalCommandArgument {ArgumentOption = "-source ", QuoteValue = true}); - _webPiInstallArguments.Add("Prerelease", new ExternalCommandArgument {ArgumentOption = "-prerelease"}); - _webPiInstallArguments.Add("_non_interactive_", new ExternalCommandArgument {ArgumentOption = "-noninteractive", Required = true}); - _webPiInstallArguments.Add("_no_cache_", new ExternalCommandArgument {ArgumentOption = "-nocache", Required = true}); + } + + public SourceType SourceType + { + get { return SourceType.webpi; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + PackageNames = ApplicationParameters.SourceRunner.WebPiPackage, + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig, logResults: false); + + if (!localPackages.ContainsKey(ApplicationParameters.SourceRunner.WebPiPackage)) + { + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + } + + public void list_noop(ChocolateyConfiguration config) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) + { + var packageResults = new ConcurrentDictionary(); + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + + //var whereToStartRecording = "---"; + //var whereToStopRecording = "--"; + //var recordingValues = false; + + Environment.ExitCode = _commandExecutor.execute( + _exePath, + args, + config.CommandExecutionTimeoutSeconds, + workingDirectory: ApplicationParameters.ShimsLocation, + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (logResults) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(_appName, logMessage)); + } + + //if (recordingValues) + //{ + // var lineParts = logMessage.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + // if (lineParts.Length > 1) + // { + // var pkgResult = new PackageResult(lineParts[0], null, null); + // packageResults.GetOrAdd(lineParts[0], pkgResult); + // } + //} + + //if (logMessage.Contains(whereToStartRecording)) recordingValues = true; + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false + ); + + return packageResults; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); + args = args.Replace(PACKAGE_NAME_TOKEN, config.PackageNames.Replace(';', ',')); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); } public ConcurrentDictionary install_run(ChocolateyConfiguration configuration, Action continueAction) { var packageInstalls = new ConcurrentDictionary(); - - var args = ExternalCommandArgsBuilder.build_arguments(configuration, _webPiInstallArguments); + var args = ExternalCommandArgsBuilder.build_arguments(configuration, _installArguments); foreach (var packageToInstall in configuration.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); var exitCode = _commandExecutor.execute( - _webPiExePath, argsForPackage, configuration.CommandExecutionTimeoutSeconds, + _exePath, + argsForPackage, + configuration.CommandExecutionTimeoutSeconds, + ApplicationParameters.ShimsLocation, (s, e) => { var logMessage = e.Data; if (string.IsNullOrWhiteSpace(logMessage)) return; - this.Log().Debug(() => " [WebPI] {0}".format_with(logMessage)); - - var packageName = get_value_from_output(logMessage, ApplicationParameters.OutputParser.Nuget.PackageName, ApplicationParameters.OutputParser.Nuget.PACKAGE_NAME_GROUP); - var packageVersion = get_value_from_output(logMessage, ApplicationParameters.OutputParser.Nuget.PackageVersion, ApplicationParameters.OutputParser.Nuget.PACKAGE_VERSION_GROUP); - - if (ApplicationParameters.OutputParser.Nuget.ResolvingDependency.IsMatch(logMessage)) - { - return; - } - - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, packageVersion, _webPiInstallArguments["_output_directory_"].ArgumentValue)); - - if (ApplicationParameters.OutputParser.Nuget.NotInstalled.IsMatch(logMessage)) - { - this.Log().Error("{0} not installed: {1}".format_with(packageName, logMessage)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); - - return; - } + this.Log().Info(() => " [{0}] {1}".format_with(_appName, logMessage)); - if (ApplicationParameters.OutputParser.Nuget.Installing.IsMatch(logMessage)) + var packageName = get_value_from_output(logMessage, ApplicationParameters.OutputParser.WebPi.PackageName, ApplicationParameters.OutputParser.WebPi.PACKAGE_NAME_GROUP); + var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + if (ApplicationParameters.OutputParser.WebPi.AlreadyInstalled.IsMatch(logMessage)) { + results.Messages.Add(new ResultMessage(ResultType.Inconclusive, packageName)); + this.Log().Warn(ChocolateyLoggers.Important, " [{0}] {1} already installed or doesn't exist. --force has no effect.".format_with(_appName, string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); return; } - if (string.IsNullOrWhiteSpace(packageName)) return; - - this.Log().Info(ChocolateyLoggers.Important, "{0} {1}".format_with(packageName, !string.IsNullOrWhiteSpace(packageVersion) ? "v" + packageVersion : string.Empty)); - - if (ApplicationParameters.OutputParser.Nuget.AlreadyInstalled.IsMatch(logMessage) && !configuration.Force) + if (ApplicationParameters.OutputParser.WebPi.Installing.IsMatch(logMessage)) { - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, packageName)); - this.Log().Warn(" Already installed."); - this.Log().Warn(ChocolateyLoggers.Important, " Use -force if you want to reinstall.".format_with(Environment.NewLine)); + this.Log().Info(ChocolateyLoggers.Important, "{0}".format_with(packageName)); return; } + + //if (string.IsNullOrWhiteSpace(packageName)) return; - results.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - if (continueAction != null) + if (ApplicationParameters.OutputParser.WebPi.Installed.IsMatch(logMessage)) { - continueAction.Invoke(results); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); } }, (s, e) => { if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Error(() => "{0}".format_with(e.Data)); + this.Log().Error(() => "[{0}] {1}".format_with(_appName, e.Data)); }, updateProcessPath: false ); @@ -147,6 +232,27 @@ public ConcurrentDictionary install_run(ChocolateyConfigu return packageInstalls; } + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(_appName)); + return new ConcurrentDictionary(); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(_appName)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(_appName)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement uninstall".format_with(_appName)); + } + /// /// Grabs a value from the output based on the regex. /// From e2c0a594df971b158b4666b1c02a1b60de15be98 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:48:36 -0500 Subject: [PATCH 07/29] (GH-14) Container registration Add the source runners configuration --- .../registration/ContainerBinding.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index fd35fb716a..507905c2b0 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -87,6 +87,16 @@ public void RegisterComponents(Container container) }; return list.AsReadOnly(); }, Lifestyle.Singleton); + + container.Register>(() => + { + var list = new List + { + container.GetInstance(), + new WebPiService(container.GetInstance(),container.GetInstance()) + }; + return list.AsReadOnly(); + }, Lifestyle.Singleton); container.Register(Lifestyle.Singleton); EventManager.initialize_with(container.GetInstance); From 45166b2b0b2c28973fd852f0a680ce79857bf591 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:50:59 -0500 Subject: [PATCH 08/29] (GH-14) ChocolateyPackageService implement source runners ChocolateyPackageService will determine based on the ISourceRunners it has in the list on ensuring that an app/tool is installed, and then use the correct source runner to perform all applicable ISourceRunner actions. --- .../services/ChocolateyPackageService.cs | 194 ++++++++++++------ .../services/IChocolateyPackageService.cs | 7 + 2 files changed, 142 insertions(+), 59 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 77f7b4e04d..bb69f1b248 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -33,6 +33,7 @@ public class ChocolateyPackageService : IChocolateyPackageService { private readonly INugetService _nugetService; private readonly IPowershellService _powershellService; + private readonly IEnumerable _sourceRunners; private readonly IShimGenerationService _shimgenService; private readonly IFileSystem _fileSystem; private readonly IRegistryService _registryService; @@ -42,12 +43,14 @@ public class ChocolateyPackageService : IChocolateyPackageService private readonly IXmlService _xmlService; public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, - IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, + IEnumerable sourceRunners, IShimGenerationService shimgenService, + IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService, IFilesService filesService, IAutomaticUninstallerService autoUninstallerService, IXmlService xmlService) { _nugetService = nugetService; _powershellService = powershellService; + _sourceRunners = sourceRunners; _shimgenService = shimgenService; _fileSystem = fileSystem; _registryService = registryService; @@ -57,32 +60,49 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p _xmlService = xmlService; } - public void list_noop(ChocolateyConfiguration config) + public void ensure_source_app_installed(ChocolateyConfiguration config) + { + perform_source_runner_action(config, r => r.ensure_source_app_installed(config, (packageResult) => handle_package_result(packageResult, config, CommandNameType.install))); + } + + private void perform_source_runner_action(ChocolateyConfiguration config, Action action) { - if (config.Sources.is_equal_to(SourceType.webpi.to_string())) + var runner = _sourceRunners.FirstOrDefault(r => r.SourceType == config.SourceType); + if (runner != null && action != null) { - //todo: webpi + action.Invoke(runner); } else { - _nugetService.list_noop(config); + this.Log().Warn("No runner was found that implements source type '{0}' or action was missing".format_with(config.SourceType.to_string())); } } + private T perform_source_runner_function(ChocolateyConfiguration config, Func function) + { + var runner = _sourceRunners.FirstOrDefault(r => r.SourceType == config.SourceType); + if (runner != null && function != null) + { + return function.Invoke(runner); + } + + this.Log().Warn("No runner was found that implements source type '{0}' or function was missing.".format_with(config.SourceType.to_string())); + return default(T); + } + + public void list_noop(ChocolateyConfiguration config) + { + perform_source_runner_action(config, r => r.list_noop(config)); + } + public void list_run(ChocolateyConfiguration config, bool logResults) { this.Log().Debug(() => "Searching for package information"); - if (config.Sources.is_equal_to(SourceType.webpi.to_string())) - { - //todo: webpi - //install webpi if not installed - //run the webpi command - this.Log().Warn("Command not yet functional, stay tuned..."); - } - else + var list = perform_source_runner_function(config, r => r.list_run(config, logResults)); + + if (config.SourceType == SourceType.normal) { - var list = _nugetService.list_run(config, logResults: true); if (config.RegularOutput) { this.Log().Warn(() => @"{0} packages {1}.".format_with(list.Count, config.ListCommand.LocalOnly ? "installed" : "found")); @@ -129,21 +149,45 @@ private void report_registry_programs(ChocolateyConfiguration config, Concurrent public void pack_noop(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for packaging."); + return; + } + _nugetService.pack_noop(config); } public void pack_run(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for packaging."); + return; + } + _nugetService.pack_run(config); } public void push_noop(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for pushing."); + return; + } + _nugetService.push_noop(config); } public void push_run(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for pushing."); + return; + } + _nugetService.push_run(config); } @@ -152,7 +196,13 @@ public void install_noop(ChocolateyConfiguration config) // each package can specify its own configuration values foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, new ConcurrentDictionary()).or_empty_list_if_null()) { - _nugetService.install_noop(packageConfig, (pkg) => _powershellService.install_noop(pkg)); + Action action = null; + if (packageConfig.SourceType == SourceType.normal) + { + action = (pkg) => _powershellService.install_noop(pkg); + } + + perform_source_runner_action(packageConfig, r => r.install_noop(packageConfig, action)); } } @@ -226,8 +276,6 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu public ConcurrentDictionary install_run(ChocolateyConfiguration config) { - //todo:are we installing from an alternate source? If so run that command instead - this.Log().Info(@"Installing the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); this.Log().Info(@"By installing you accept licenses for the packages."); @@ -236,10 +284,13 @@ public ConcurrentDictionary install_run(ChocolateyConfigu foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null()) { - var results = _nugetService.install_run( - packageConfig, - (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install) - ); + Action action = null; + if (packageConfig.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); + } + var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action)); + foreach (var result in results) { packageInstalls.GetOrAdd(result.Key, result.Value); @@ -293,6 +344,12 @@ Would have determined packages that are out of date based on what is public void outdated_run(ChocolateyConfiguration config) { + if (config.SourceType != SourceType.normal) + { + this.Log().Warn(ChocolateyLoggers.Important, "This source doesn't provide a facility for outdated."); + return; + } + this.Log().Info(ChocolateyLoggers.Important, @"Outdated Packages Output is package name | current version | available version | pinned? "); @@ -380,7 +437,13 @@ private IEnumerable get_packages_from_config(string pac public void upgrade_noop(ChocolateyConfiguration config) { - var noopUpgrades = _nugetService.upgrade_noop(config, (pkg) => _powershellService.install_noop(pkg)); + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (pkg) => _powershellService.install_noop(pkg); + } + + var noopUpgrades = perform_source_runner_function(config, r => r.upgrade_noop(config, action)); if (config.RegularOutput) { var upgradeWarnings = noopUpgrades.Count(p => p.Value.Warning); @@ -406,8 +469,6 @@ public void upgrade_noop(ChocolateyConfiguration config) public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config) { - //todo:are we upgrading an alternate source? If so run that command instead - this.Log().Info(@"Upgrading the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); this.Log().Info(@"By upgrading you accept licenses for the packages."); @@ -417,10 +478,13 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu throw new ApplicationException("A packages.config file is only used with installs."); } - var packageUpgrades = _nugetService.upgrade_run( - config, - (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade) - ); + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade); + } + + var packageUpgrades = perform_source_runner_function(config, r => r.upgrade_run(config, action)); var upgradeFailures = packageUpgrades.Count(p => !p.Value.Success); var upgradeWarnings = packageUpgrades.Count(p => p.Value.Warning); @@ -462,7 +526,13 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu public void uninstall_noop(ChocolateyConfiguration config) { - _nugetService.uninstall_noop(config, (pkg) => _powershellService.uninstall_noop(pkg)); + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (pkg) => _powershellService.uninstall_noop(pkg); + } + + perform_source_runner_action(config, r => r.uninstall_noop(config, action)); } public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config) @@ -475,37 +545,13 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi throw new ApplicationException("A packages.config file is only used with installs."); } - var packageUninstalls = _nugetService.uninstall_run( - config, - (packageResult) => - { - if (!_fileSystem.directory_exists(packageResult.InstallLocation)) - { - packageResult.InstallLocation += ".{0}".format_with(packageResult.Package.Version.to_string()); - } - - _shimgenService.uninstall(config, packageResult); - - if (!config.SkipPackageInstallProvider) - { - _powershellService.uninstall(config, packageResult); - } - - _autoUninstallerService.run(packageResult, config); - - if (packageResult.Success) - { - //todo: v2 clean up package information store for things no longer installed (call it compact?) - uninstall_cleanup(config, packageResult); - } - else - { - this.Log().Error(ChocolateyLoggers.Important, "{0} {1} not successful.".format_with(packageResult.Name, "uninstall")); - handle_unsuccessful_operation(config, packageResult, movePackageToFailureLocation: false, attemptRollback: false); - } + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_uninstall(packageResult, config); + } - //todo:prevent reboots - }); + var packageUninstalls = perform_source_runner_function(config, r => r.uninstall_run(config, action)); var uninstallFailures = packageUninstalls.Count(p => !p.Value.Success); this.Log().Warn(() => @"{0}{1} uninstalled {2}/{3} packages. {4} packages failed.{0} See the log for details ({5}).".format_with( @@ -534,6 +580,36 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi return packageUninstalls; } + public void handle_package_uninstall(PackageResult packageResult, ChocolateyConfiguration config) + { + if (!_fileSystem.directory_exists(packageResult.InstallLocation)) + { + packageResult.InstallLocation += ".{0}".format_with(packageResult.Package.Version.to_string()); + } + + _shimgenService.uninstall(config, packageResult); + + if (!config.SkipPackageInstallProvider) + { + _powershellService.uninstall(config, packageResult); + } + + _autoUninstallerService.run(packageResult, config); + + if (packageResult.Success) + { + //todo: v2 clean up package information store for things no longer installed (call it compact?) + uninstall_cleanup(config, packageResult); + } + else + { + this.Log().Error(ChocolateyLoggers.Important, "{0} {1} not successful.".format_with(packageResult.Name, "uninstall")); + handle_unsuccessful_operation(config, packageResult, movePackageToFailureLocation: false, attemptRollback: false); + } + + //todo:prevent reboots + } + private void uninstall_cleanup(ChocolateyConfiguration config, PackageResult packageResult) { _packageInfoService.remove_package_information(packageResult.Package); diff --git a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs index 45b0e2f206..51d17671f7 100644 --- a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs @@ -24,6 +24,13 @@ namespace chocolatey.infrastructure.app.services /// public interface IChocolateyPackageService { + + /// + /// Ensures the application that controls a source is installed + /// + /// The configuration. + void ensure_source_app_installed(ChocolateyConfiguration config); + /// /// Run list in noop mode /// From 60172999e74155930b047736808071858e8e1c79 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:52:21 -0500 Subject: [PATCH 09/29] (GH-14) ensure_source_app - called by commands For each command that has run of a command that is listed in ISourceRunners, implement a check to ensure that the application that normally runs for that tool is installed. --- .../infrastructure.app/commands/ChocolateyInstallCommand.cs | 1 + .../infrastructure.app/commands/ChocolateyListCommand.cs | 1 + .../infrastructure.app/commands/ChocolateyUninstallCommand.cs | 1 + .../infrastructure.app/commands/ChocolateyUpgradeCommand.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index c26cbe973c..f3cf20b6df 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -171,6 +171,7 @@ public void noop(ChocolateyConfiguration configuration) public void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.install_run(configuration); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs index 7d12c3cde1..fc92e3d41a 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs @@ -114,6 +114,7 @@ public void noop(ChocolateyConfiguration configuration) public void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.list_run(configuration, logResults: true); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs index e1e4134a57..54b2609112 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs @@ -128,6 +128,7 @@ public void noop(ChocolateyConfiguration configuration) public void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.uninstall_run(configuration); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index 11c10bf708..a5b0763faf 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -147,6 +147,7 @@ public virtual void noop(ChocolateyConfiguration configuration) public virtual void run(ChocolateyConfiguration configuration) { + _packageService.ensure_source_app_installed(configuration); _packageService.upgrade_run(configuration); } From b02e4145050d90a4d1ee06f6cfbcf897ef21d760 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 00:53:00 -0500 Subject: [PATCH 10/29] (GH-14) Uninstall adds source When uninstalling from the special sources, those should be able to be specified. --- .../infrastructure.app/commands/ChocolateyUninstallCommand.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs index 54b2609112..a9d545efbd 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs @@ -38,6 +38,9 @@ public ChocolateyUninstallCommand(IChocolateyPackageService packageService) public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) { optionSet + .Add("s=|source=", + "Source - The source to find the package(s) to install. Special sources include: ruby, webpi, cygwin, windowsfeatures, and python. Defaults to default feeds.", + option => configuration.Sources = option) .Add("version=", "Version - A specific version to uninstall. Defaults to unspecified.", option => configuration.Version = option.remove_surrounding_quotes()) From dd2afd20c153b7840e9a34f32ca244e877c43f6f Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 02:04:04 -0500 Subject: [PATCH 11/29] (refactor) naming --- .../infrastructure.app/services/WebPiService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 88e9b6f6ea..9ae01edd40 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -47,14 +47,14 @@ public WebPiService(ICommandExecutor commandExecutor, INugetService nugetService /// private void set_cmd_args_dictionaries() { - set_webpi_list_dictionary(_listArguments); - set_webpi_install_dictionary(_installArguments); + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); } /// /// Sets webpicmd list dictionary /// - private void set_webpi_list_dictionary(IDictionary args) + private void set_list_dictionary(IDictionary args) { args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/List", Required = true}); args.Add("_list_option_", new ExternalCommandArgument {ArgumentOption = "/ListOption:All", Required = true}); @@ -63,7 +63,7 @@ private void set_webpi_list_dictionary(IDictionary /// Sets webpicmd install dictionary /// - private void set_webpi_install_dictionary(IDictionary args) + private void set_install_dictionary(IDictionary args) { args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "/Install", Required = true}); args.Add("_accept_eula_", new ExternalCommandArgument {ArgumentOption = "/AcceptEula", Required = true}); From 3661276fd767f35e3e9c10b741d26e0807a0a510 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 02:17:29 -0500 Subject: [PATCH 12/29] (GH-14)(refactor) move consts to WebPiService The constants for WebPI Service are only used by WebPi Service, so there is no reason to put them and all of the other special source's arguments inside of ApplicationParameters. Move those over to the actual source service. --- .../ApplicationParameters.cs | 20 -------- .../services/WebPiService.cs | 50 +++++++++++-------- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index c6d94a64f8..a546a87287 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -87,26 +87,6 @@ public static class Messages public static readonly string NugetEventActionHeader = "Nuget called an event"; } - public static class SourceRunner - { - public static readonly string WebPiName = "Web Platform Installer"; - public static readonly string WebPiPackage = "webpicmd"; - public static readonly string WebPiExe = "webpicmd.exe"; - } - - public static class OutputParser - { - public static class WebPi - { - public const string PACKAGE_NAME_GROUP = "PkgName"; - public static readonly Regex Installing =new Regex(@"Started installing:", RegexOptions.Compiled); - public static readonly Regex Installed = new Regex(@"Install completed \(Success\):", RegexOptions.Compiled); - public static readonly Regex AlreadyInstalled = new Regex(@"No products to be installed \(either not available or already installed\)", RegexOptions.Compiled); - //public static readonly Regex NotInstalled = new Regex(@"not installed", RegexOptions.Compiled); - public static readonly Regex PackageName = new Regex(@"'(?<{0}>[^']*)'".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); - } - } - private static T try_get_config(Func func, T defaultValue) { try diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 9ae01edd40..02fa1cae95 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -30,8 +30,16 @@ public sealed class WebPiService : ISourceRunner private readonly ICommandExecutor _commandExecutor; private readonly INugetService _nugetService; private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; - private readonly string _exePath = ApplicationParameters.SourceRunner.WebPiExe; - private readonly string _appName = ApplicationParameters.SourceRunner.WebPiName; + private const string EXE_PATH = "webpicmd.exe"; + private const string APP_NAME = "Web Platform Installer"; + public const string WEB_PI_PACKAGE = "webpicmd"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstallingRegex = new Regex(@"Started installing:", RegexOptions.Compiled); + public static readonly Regex InstalledRegex = new Regex(@"Install completed \(Success\):", RegexOptions.Compiled); + public static readonly Regex AlreadyInstalledRegex = new Regex(@"No products to be installed \(either not available or already installed\)", RegexOptions.Compiled); + //public static readonly Regex NotInstalled = new Regex(@"not installed", RegexOptions.Compiled); + public static readonly Regex PackageNameRegex = new Regex(@"'(?<{0}>[^']*)'".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); @@ -86,7 +94,7 @@ public void ensure_source_app_installed(ChocolateyConfiguration config, Action

list_run(ChocolateyConfiguration config, bool logResults) @@ -128,7 +136,7 @@ public ConcurrentDictionary list_run(ChocolateyConfigurat //var recordingValues = false; Environment.ExitCode = _commandExecutor.execute( - _exePath, + EXE_PATH, args, config.CommandExecutionTimeoutSeconds, workingDirectory: ApplicationParameters.ShimsLocation, @@ -142,7 +150,7 @@ public ConcurrentDictionary list_run(ChocolateyConfigurat } else { - this.Log().Debug(() => "[{0}] {1}".format_with(_appName, logMessage)); + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); } //if (recordingValues) @@ -172,7 +180,7 @@ public void install_noop(ChocolateyConfiguration config, Action c { var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); args = args.Replace(PACKAGE_NAME_TOKEN, config.PackageNames.Replace(';', ',')); - this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); } public ConcurrentDictionary install_run(ChocolateyConfiguration configuration, Action continueAction) @@ -184,7 +192,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); var exitCode = _commandExecutor.execute( - _exePath, + EXE_PATH, argsForPackage, configuration.CommandExecutionTimeoutSeconds, ApplicationParameters.ShimsLocation, @@ -192,26 +200,24 @@ public ConcurrentDictionary install_run(ChocolateyConfigu { var logMessage = e.Data; if (string.IsNullOrWhiteSpace(logMessage)) return; - this.Log().Info(() => " [{0}] {1}".format_with(_appName, logMessage)); + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); - var packageName = get_value_from_output(logMessage, ApplicationParameters.OutputParser.WebPi.PackageName, ApplicationParameters.OutputParser.WebPi.PACKAGE_NAME_GROUP); + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); - if (ApplicationParameters.OutputParser.WebPi.AlreadyInstalled.IsMatch(logMessage)) + if (AlreadyInstalledRegex.IsMatch(logMessage)) { results.Messages.Add(new ResultMessage(ResultType.Inconclusive, packageName)); - this.Log().Warn(ChocolateyLoggers.Important, " [{0}] {1} already installed or doesn't exist. --force has no effect.".format_with(_appName, string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + this.Log().Warn(ChocolateyLoggers.Important, " [{0}] {1} already installed or doesn't exist. --force has no effect.".format_with(APP_NAME, string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); return; } - if (ApplicationParameters.OutputParser.WebPi.Installing.IsMatch(logMessage)) + if (InstallingRegex.IsMatch(logMessage)) { this.Log().Info(ChocolateyLoggers.Important, "{0}".format_with(packageName)); return; } - //if (string.IsNullOrWhiteSpace(packageName)) return; - - if (ApplicationParameters.OutputParser.WebPi.Installed.IsMatch(logMessage)) + if (InstalledRegex.IsMatch(logMessage)) { this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); } @@ -219,7 +225,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu (s, e) => { if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Error(() => "[{0}] {1}".format_with(_appName, e.Data)); + this.Log().Error(() => "[{0}] {1}".format_with(APP_NAME, e.Data)); }, updateProcessPath: false ); @@ -234,23 +240,23 @@ public ConcurrentDictionary install_run(ChocolateyConfigu public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) { - this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(_appName)); + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); return new ConcurrentDictionary(); } public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) { - throw new NotImplementedException("{0} does not implement upgrade".format_with(_appName)); + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); } public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) { - this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(_appName)); + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); } public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) { - throw new NotImplementedException("{0} does not implement uninstall".format_with(_appName)); + throw new NotImplementedException("{0} does not implement uninstall".format_with(APP_NAME)); } ///

From 6fce571fbc97b289d9b25086fba77901fcb84d7e Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 21 May 2015 02:36:31 -0500 Subject: [PATCH 13/29] (GH-14) Ruby Gems source This implements list and install for Ruby Gems. It does not implement the ability to upgrade or uninstall applications that it installs. --- src/chocolatey/chocolatey.csproj | 1 + .../registration/ContainerBinding.cs | 3 +- .../services/RubyGemsService.cs | 274 ++++++++++++++++++ 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 src/chocolatey/infrastructure.app/services/RubyGemsService.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index df979ddb99..2525069c42 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -110,6 +110,7 @@ + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 507905c2b0..56ca6ab7ea 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -93,7 +93,8 @@ public void RegisterComponents(Container container) var list = new List { container.GetInstance(), - new WebPiService(container.GetInstance(),container.GetInstance()) + new WebPiService(container.GetInstance(),container.GetInstance()), + new RubyGemsService(container.GetInstance(),container.GetInstance()) }; return list.AsReadOnly(); }, Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs new file mode 100644 index 0000000000..0f82bb6025 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -0,0 +1,274 @@ +namespace chocolatey.infrastructure.app.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Text.RegularExpressions; + using configuration; + using domain; + using infrastructure.commands; + using logging; + using results; + + public sealed class RubyGemsService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string EXE_PATH = "cmd.exe"; + private const string APP_NAME = "Ruby Gems"; + public const string RUBY_PORTABLE_PACKAGE = "ruby.portable"; + public const string RUBY_PACKAGE = "ruby"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstallingRegex = new Regex(@"Fetching:", RegexOptions.Compiled); + public static readonly Regex InstalledRegex = new Regex(@"Successfully installed", RegexOptions.Compiled); + public static readonly Regex ErrorNotFoundRegex = new Regex(@"ERROR: Could not find a valid gem", RegexOptions.Compiled); + public static readonly Regex PackageNameFetchingRegex = new Regex(@"Fetching: (?<{0}>.*)\-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + public static readonly Regex PackageNameInstalledRegex = new Regex(@"Successfully installed (?<{0}>.*)\-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + public static readonly Regex PackageNameErrorRegex = new Regex(@"'(?<{0}>[^']*)'".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + + + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public RubyGemsService(ICommandExecutor commandExecutor, INugetService nugetService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); + } + + /// + /// Sets webpicmd list dictionary + /// + private void set_list_dictionary(IDictionary args) + { + args.Add("_cmd_c_", new ExternalCommandArgument { ArgumentOption = "/c", Required = true }); + args.Add("_gem_", new ExternalCommandArgument { ArgumentOption = "gem", Required = true }); + args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "list", Required = true}); + } + + /// + /// Sets webpicmd install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + args.Add("_cmd_c_", new ExternalCommandArgument {ArgumentOption = "/c", Required = true}); + args.Add("_gem_", new ExternalCommandArgument {ArgumentOption = "gem", Required = true}); + args.Add("_action_", new ExternalCommandArgument {ArgumentOption = "install", Required = true}); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "package name ", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + + args.Add("Force", new ExternalCommandArgument + { + ArgumentOption = "-f ", + QuoteValue = false, + Required = false + }); + + args.Add("Version", new ExternalCommandArgument + { + ArgumentOption = "--version ", + QuoteValue = false, + Required = false + }); + } + + public SourceType SourceType + { + get { return SourceType.ruby; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + PackageNames = RUBY_PORTABLE_PACKAGE, + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig, logResults: false); + + if (!localPackages.ContainsKey(RUBY_PACKAGE) && !localPackages.ContainsKey(RUBY_PORTABLE_PACKAGE)) + { + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + } + + public void list_noop(ChocolateyConfiguration config) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); + } + + public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) + { + var packageResults = new ConcurrentDictionary(); + var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); + + Environment.ExitCode = _commandExecutor.execute( + EXE_PATH, + args, + config.CommandExecutionTimeoutSeconds, + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (logResults) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false + ); + + return packageResults; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); + args = args.Replace(PACKAGE_NAME_TOKEN, config.PackageNames.Replace(';', ',')); + this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration configuration, Action continueAction) + { + var packageInstalls = new ConcurrentDictionary(); + var args = ExternalCommandArgsBuilder.build_arguments(configuration, _installArguments); + + foreach (var packageToInstall in configuration.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + var exitCode = _commandExecutor.execute( + EXE_PATH, + argsForPackage, + configuration.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (InstallingRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameFetchingRegex, PACKAGE_NAME_GROUP); + var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + this.Log().Info(ChocolateyLoggers.Important, "{0}".format_with(packageName)); + return; + } + + //if (string.IsNullOrWhiteSpace(packageName)) return; + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameInstalledRegex, PACKAGE_NAME_GROUP); + var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + var packageName = get_value_from_output(logMessage, PackageNameErrorRegex, PACKAGE_NAME_GROUP); + + if (ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + return packageInstalls; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement uninstall".format_with(APP_NAME)); + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file From 95b4b90695497621f624e5a4ecdca6deb7866c2c Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 25 May 2015 21:52:38 -0500 Subject: [PATCH 14/29] (refactor) naming/comments --- .../services/RubyGemsService.cs | 23 ++++++++++--------- .../services/WebPiService.cs | 19 +++++++-------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs index 0f82bb6025..bb922cd9c0 100644 --- a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -48,7 +48,7 @@ private void set_cmd_args_dictionaries() } /// - /// Sets webpicmd list dictionary + /// Sets list dictionary /// private void set_list_dictionary(IDictionary args) { @@ -58,7 +58,7 @@ private void set_list_dictionary(IDictionary ar } /// - /// Sets webpicmd install dictionary + /// Sets install dictionary /// private void set_install_dictionary(IDictionary args) { @@ -170,18 +170,18 @@ public void install_noop(ChocolateyConfiguration config, Action c this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); } - public ConcurrentDictionary install_run(ChocolateyConfiguration configuration, Action continueAction) + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { - var packageInstalls = new ConcurrentDictionary(); - var args = ExternalCommandArgsBuilder.build_arguments(configuration, _installArguments); + var packageResults = new ConcurrentDictionary(); + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); - foreach (var packageToInstall in configuration.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); var exitCode = _commandExecutor.execute( EXE_PATH, argsForPackage, - configuration.CommandExecutionTimeoutSeconds, + config.CommandExecutionTimeoutSeconds, (s, e) => { var logMessage = e.Data; @@ -191,7 +191,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu if (InstallingRegex.IsMatch(logMessage)) { var packageName = get_value_from_output(logMessage, PackageNameFetchingRegex, PACKAGE_NAME_GROUP); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); this.Log().Info(ChocolateyLoggers.Important, "{0}".format_with(packageName)); return; } @@ -201,7 +201,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu if (InstalledRegex.IsMatch(logMessage)) { var packageName = get_value_from_output(logMessage, PackageNameInstalledRegex, PACKAGE_NAME_GROUP); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); @@ -217,7 +217,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu if (ErrorNotFoundRegex.IsMatch(logMessage)) { - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); } }, @@ -229,7 +229,8 @@ public ConcurrentDictionary install_run(ChocolateyConfigu Environment.ExitCode = exitCode; } } - return packageInstalls; + + return packageResults; } public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 02fa1cae95..1f880e30c4 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -60,7 +60,7 @@ private void set_cmd_args_dictionaries() } /// - /// Sets webpicmd list dictionary + /// Sets list dictionary /// private void set_list_dictionary(IDictionary args) { @@ -69,7 +69,7 @@ private void set_list_dictionary(IDictionary ar } /// - /// Sets webpicmd install dictionary + /// Sets install dictionary /// private void set_install_dictionary(IDictionary args) { @@ -183,18 +183,18 @@ public void install_noop(ChocolateyConfiguration config, Action c this.Log().Info("Would have run '{0} {1}'".format_with(EXE_PATH, args)); } - public ConcurrentDictionary install_run(ChocolateyConfiguration configuration, Action continueAction) + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { - var packageInstalls = new ConcurrentDictionary(); - var args = ExternalCommandArgsBuilder.build_arguments(configuration, _installArguments); + var packageResults = new ConcurrentDictionary(); + var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); - foreach (var packageToInstall in configuration.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); var exitCode = _commandExecutor.execute( EXE_PATH, argsForPackage, - configuration.CommandExecutionTimeoutSeconds, + config.CommandExecutionTimeoutSeconds, ApplicationParameters.ShimsLocation, (s, e) => { @@ -203,7 +203,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); if (AlreadyInstalledRegex.IsMatch(logMessage)) { results.Messages.Add(new ResultMessage(ResultType.Inconclusive, packageName)); @@ -235,7 +235,8 @@ public ConcurrentDictionary install_run(ChocolateyConfigu Environment.ExitCode = exitCode; } } - return packageInstalls; + + return packageResults; } public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) From e9b4fb2440c0e21ff68a42f6ac4b4d9e704faa30 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 25 May 2015 21:53:38 -0500 Subject: [PATCH 15/29] (GH-14) Windows Features Source - Add List - Add search by name - add install - add uninstall --- src/chocolatey/chocolatey.csproj | 1 + .../registration/ContainerBinding.cs | 1 + .../services/WindowsFeatureService.cs | 376 ++++++++++++++++++ 3 files changed, 378 insertions(+) create mode 100644 src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 2525069c42..ba82ad0c75 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -111,6 +111,7 @@ + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 56ca6ab7ea..25ef8839fa 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -94,6 +94,7 @@ public void RegisterComponents(Container container) { container.GetInstance(), new WebPiService(container.GetInstance(),container.GetInstance()), + new WindowsFeatureService(container.GetInstance(),container.GetInstance(),container.GetInstance()), new RubyGemsService(container.GetInstance(),container.GetInstance()) }; return list.AsReadOnly(); diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs new file mode 100644 index 0000000000..44cfc8588c --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -0,0 +1,376 @@ +namespace chocolatey.infrastructure.app.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using platforms; + using results; + + /// + /// Alternative Source for Enabling Windows Features + /// + /// + /// https://technet.microsoft.com/en-us/library/hh825265.aspx?f=255&MSPPError=-2147217396 + /// + public sealed class WindowsFeatureService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private const string ALL_TOKEN = "{{all}}"; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string LOG_LEVEL_TOKEN = "{{loglevel}}"; + private const string LOG_LEVEL_INFO = "3"; + private const string LOG_LEVEL_DEBUG = "4"; + private const string FEATURES_VALUE = "/Get-Features"; + private const string FORMAT_VALUE = "/Format:Table"; + private string _exePath = string.Empty; + + private static readonly IList _exeLocations = new List + { + Environment.ExpandEnvironmentVariables("%systemroot%\\sysnative\\dism.exe"), + Environment.ExpandEnvironmentVariables("%systemroot%\\System32\\dism.exe"), + "dism.exe" + }; + + private const string APP_NAME = "Windows Features"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"The operation completed successfully.", RegexOptions.Compiled); + public static readonly Regex ErrorRegex = new Regex(@"Error:", RegexOptions.Compiled); + public static readonly Regex ErrorNotFoundRegex = new Regex(@"Feature name .* is unknown", RegexOptions.Compiled); + + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _uninstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public WindowsFeatureService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); + set_uninstall_dictionary(_uninstallArguments); + } + + /// + /// Sets list dictionary + /// + private void set_list_dictionary(IDictionary args) + { + set_common_args(args); + args.Add("_features_", new ExternalCommandArgument { ArgumentOption = FEATURES_VALUE, Required = true }); + args.Add("_format_", new ExternalCommandArgument {ArgumentOption = FORMAT_VALUE, Required = true}); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + set_common_args(args); + + args.Add("_all_", new ExternalCommandArgument { ArgumentOption = ALL_TOKEN, Required = true }); + args.Add("_feature_", new ExternalCommandArgument {ArgumentOption = "/Enable-Feature", Required = true}); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "/FeatureName:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); + } + + /// + /// Sets uninstall dictionary + /// + private void set_uninstall_dictionary(IDictionary args) + { + set_common_args(args); + + // uninstall feature completely in 8/2012+ - /Remove + // would need /source to bring it back + + args.Add("_feature_", new ExternalCommandArgument {ArgumentOption = "/Disable-Feature", Required = true}); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "/FeatureName:", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); + } + + private void set_common_args(IDictionary args) + { + args.Add("_online_", new ExternalCommandArgument { ArgumentOption = "/Online", Required = true }); + args.Add("_english_", new ExternalCommandArgument { ArgumentOption = "/English", Required = true }); + args.Add("_loglevel_", new ExternalCommandArgument + { + ArgumentOption = "/LogLevel=", + ArgumentValue = LOG_LEVEL_TOKEN, + QuoteValue = false, + Required = true + }); + + args.Add("_no_restart_", new ExternalCommandArgument { ArgumentOption = "/NoRestart", Required = true }); + } + + public SourceType SourceType + { + get { return SourceType.windowsfeatures; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + set_executable_path_if_not_set(); + } + + public void set_executable_path_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_exePath)) return; + + foreach (var location in _exeLocations) + { + if (_fileSystem.file_exists(location)) + { + _exePath = location; + break; + } + } + + if (string.IsNullOrWhiteSpace(_exePath)) throw new FileNotFoundException("Unable to find suitable location for the executable. Searched the following locations: '{0}'".format_with(string.Join("; ", _exeLocations))); + } + + public void list_noop(ChocolateyConfiguration config) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + var packageResults = new ConcurrentDictionary(); + + Environment.ExitCode = _commandExecutor.execute( + _exePath, + args, + config.CommandExecutionTimeoutSeconds, + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (logResults) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false + ); + + return packageResults; + } + + public string build_args(ChocolateyConfiguration config,IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + // at least Windows 8/2012 + if (config.Information.PlatformVersion.Major >= 6 && config.Information.PlatformVersion.Minor >= 2) + { + args = args.Replace(ALL_TOKEN, "/All"); + } + else + { + args = args.Replace(ALL_TOKEN, string.Empty); + } + + if (!string.IsNullOrWhiteSpace(config.Input)) + { + args = args.Replace(FEATURES_VALUE, "/Get-FeatureInfo").Replace(FORMAT_VALUE, "/FeatureName:{0}".format_with(config.Input)); + } + + args = args.Replace(LOG_LEVEL_TOKEN, config.Debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO); + + return args; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(); + + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + { + var packageName = packageToInstall; + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageName); + + //todo: detect windows feature is already enabled + /* + $checkStatement=@" + `$dismInfo=(cmd /c `"$dism /Online /Get-FeatureInfo /FeatureName:$packageName`") + if(`$dismInfo -contains 'State : Enabled') {return} + if(`$dismInfo -contains 'State : Enable Pending') {return} + "@ + */ + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + var packageResults = new ConcurrentDictionary(); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) + { + var packageName = packageToInstall; + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageName); + + //todo: detect windows feature is already disabled + /* + $checkStatement=@" + `$dismInfo=(cmd /c `"$dism /Online /Get-FeatureInfo /FeatureName:$packageName`") + if(`$dismInfo -contains 'State : Disabled') {return} + if(`$dismInfo -contains 'State : Disable Pending') {return} + "@ + */ + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + } +} \ No newline at end of file From 413da2b1de68c075505370fa9e15ade278b355c1 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 2 Jun 2015 14:44:31 -0500 Subject: [PATCH 16/29] (doc) WindowsFeatures - more links --- .../infrastructure.app/services/WindowsFeatureService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs index 44cfc8588c..fd77945137 100644 --- a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -18,6 +18,8 @@ /// /// /// https://technet.microsoft.com/en-us/library/hh825265.aspx?f=255&MSPPError=-2147217396 + /// Win 7 - https://technet.microsoft.com/en-us/library/dd744311.aspx + /// Maybe Win2003/2008 - http://www.wincert.net/forum/files/file/8-deployment-image-servicing-and-management-dism/ | http://wincert.net/leli55PK/DISM/ /// public sealed class WindowsFeatureService : ISourceRunner { From 7ae1939f734615357247c6a518c52a76a9869077 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 2 Jun 2015 15:17:54 -0500 Subject: [PATCH 17/29] (maint) formatting --- .../services/WindowsFeatureService.cs | 90 +++++++++++-------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs index fd77945137..ce91cdd8ae 100644 --- a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -1,4 +1,19 @@ -namespace chocolatey.infrastructure.app.services +// Copyright © 2011 - Present 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.services { using System; using System.Collections.Concurrent; @@ -10,16 +25,15 @@ using filesystem; using infrastructure.commands; using logging; - using platforms; using results; /// - /// Alternative Source for Enabling Windows Features + /// Alternative Source for Enabling Windows Features /// /// - /// https://technet.microsoft.com/en-us/library/hh825265.aspx?f=255&MSPPError=-2147217396 - /// Win 7 - https://technet.microsoft.com/en-us/library/dd744311.aspx - /// Maybe Win2003/2008 - http://www.wincert.net/forum/files/file/8-deployment-image-servicing-and-management-dism/ | http://wincert.net/leli55PK/DISM/ + /// https://technet.microsoft.com/en-us/library/hh825265.aspx?f=255&MSPPError=-2147217396 + /// Win 7 - https://technet.microsoft.com/en-us/library/dd744311.aspx + /// Maybe Win2003/2008 - http://www.wincert.net/forum/files/file/8-deployment-image-servicing-and-management-dism/ | http://wincert.net/leli55PK/DISM/ /// public sealed class WindowsFeatureService : ISourceRunner { @@ -76,7 +90,7 @@ private void set_cmd_args_dictionaries() private void set_list_dictionary(IDictionary args) { set_common_args(args); - args.Add("_features_", new ExternalCommandArgument { ArgumentOption = FEATURES_VALUE, Required = true }); + args.Add("_features_", new ExternalCommandArgument {ArgumentOption = FEATURES_VALUE, Required = true}); args.Add("_format_", new ExternalCommandArgument {ArgumentOption = FORMAT_VALUE, Required = true}); } @@ -85,9 +99,9 @@ private void set_list_dictionary(IDictionary ar /// private void set_install_dictionary(IDictionary args) { - set_common_args(args); + set_common_args(args); - args.Add("_all_", new ExternalCommandArgument { ArgumentOption = ALL_TOKEN, Required = true }); + args.Add("_all_", new ExternalCommandArgument {ArgumentOption = ALL_TOKEN, Required = true}); args.Add("_feature_", new ExternalCommandArgument {ArgumentOption = "/Enable-Feature", Required = true}); args.Add("_package_name_", new ExternalCommandArgument { @@ -103,7 +117,7 @@ private void set_install_dictionary(IDictionary /// private void set_uninstall_dictionary(IDictionary args) { - set_common_args(args); + set_common_args(args); // uninstall feature completely in 8/2012+ - /Remove // would need /source to bring it back @@ -120,8 +134,8 @@ private void set_uninstall_dictionary(IDictionary args) { - args.Add("_online_", new ExternalCommandArgument { ArgumentOption = "/Online", Required = true }); - args.Add("_english_", new ExternalCommandArgument { ArgumentOption = "/English", Required = true }); + args.Add("_online_", new ExternalCommandArgument {ArgumentOption = "/Online", Required = true}); + args.Add("_english_", new ExternalCommandArgument {ArgumentOption = "/English", Required = true}); args.Add("_loglevel_", new ExternalCommandArgument { ArgumentOption = "/LogLevel=", @@ -130,7 +144,7 @@ private void set_common_args(IDictionary args) Required = true }); - args.Add("_no_restart_", new ExternalCommandArgument { ArgumentOption = "/NoRestart", Required = true }); + args.Add("_no_restart_", new ExternalCommandArgument {ArgumentOption = "/NoRestart", Required = true}); } public SourceType SourceType @@ -146,7 +160,7 @@ public void ensure_source_app_installed(ChocolateyConfiguration config, Action

list_run(ChocolateyConfigurat return packageResults; } - public string build_args(ChocolateyConfiguration config,IDictionary argsDictionary) + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) { var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); @@ -234,9 +248,9 @@ public void install_noop(ChocolateyConfiguration config, Action c public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { set_executable_path_if_not_set(); - var args = build_args(config, _installArguments); + var args = build_args(config, _installArguments); var packageResults = new ConcurrentDictionary(); - + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { var packageName = packageToInstall; @@ -319,7 +333,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi var args = build_args(config, _uninstallArguments); var packageResults = new ConcurrentDictionary(); - foreach (var packageToInstall in config.PackageNames.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { var packageName = packageToInstall; var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); @@ -339,30 +353,30 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi argsForPackage, config.CommandExecutionTimeoutSeconds, (s, e) => - { - var logMessage = e.Data; - if (string.IsNullOrWhiteSpace(logMessage)) return; - this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); - - if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) { - results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); - } + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); - if (InstalledRegex.IsMatch(logMessage)) - { - results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); - this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); - } - }, + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Error, packageName)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, (s, e) => - { - var logMessage = e.Data; - if (string.IsNullOrWhiteSpace(logMessage)) return; - this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); - }, + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + }, updateProcessPath: false ); From 10b4f8ce799d93173d89b32eca0508bfb738dcb5 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 2 Jun 2015 16:14:18 -0500 Subject: [PATCH 18/29] (GH-14) rename registry exists to installer keys Rename the registry service `value_exists` to `installer_value_exists` to clarify what it is doing. --- .../services/AutomaticUninstallerServiceSpecs.cs | 6 +++--- .../services/AutomaticUninstallerService.cs | 4 ++-- .../infrastructure.app/services/IRegistryService.cs | 2 +- .../infrastructure.app/services/RegistryService.cs | 3 +-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs index 6cfaad16a4..1cebc0d7ef 100644 --- a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs @@ -77,7 +77,7 @@ public override void Context() packageResults.GetOrAdd("regular", packageResult); fileSystem.Setup(f => f.directory_exists(registryKeys.FirstOrDefault().InstallLocation)).Returns(true); - registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(true); + registryService.Setup(r => r.installer_value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(true); fileSystem.Setup(f => f.get_full_path(expectedUninstallString)).Returns(expectedUninstallString); } } @@ -235,7 +235,7 @@ public override void Context() { base.Context(); registryService.ResetCalls(); - registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); + registryService.Setup(r => r.installer_value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); } public override void Because() @@ -264,7 +264,7 @@ public override void Context() fileSystem.ResetCalls(); fileSystem.Setup(f => f.directory_exists(registryKeys.FirstOrDefault().InstallLocation)).Returns(false); registryService.ResetCalls(); - registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); + registryService.Setup(r => r.installer_value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); } public override void Because() diff --git a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs index 71997f9704..5367f46fbd 100644 --- a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs +++ b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs @@ -79,11 +79,11 @@ public void run(PackageResult packageResult, ChocolateyConfiguration config) { this.Log().Debug(() => " Preparing uninstall key '{0}'".format_with(key.UninstallString)); - if ((!string.IsNullOrWhiteSpace(key.InstallLocation) && !_fileSystem.directory_exists(key.InstallLocation)) || !_registryService.value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation)) + if ((!string.IsNullOrWhiteSpace(key.InstallLocation) && !_fileSystem.directory_exists(key.InstallLocation)) || !_registryService.installer_value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation)) { this.Log().Info(" Skipping auto uninstaller - The application appears to have been uninstalled already by other means."); this.Log().Debug(() => " Searched for install path '{0}' - found? {1}".format_with(key.InstallLocation.escape_curly_braces(), _fileSystem.directory_exists(key.InstallLocation))); - this.Log().Debug(() => " Searched for registry key '{0}' value '{1}' - found? {2}".format_with(key.KeyPath.escape_curly_braces(), ApplicationParameters.RegistryValueInstallLocation, _registryService.value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation))); + this.Log().Debug(() => " Searched for registry key '{0}' value '{1}' - found? {2}".format_with(key.KeyPath.escape_curly_braces(), ApplicationParameters.RegistryValueInstallLocation, _registryService.installer_value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation))); continue; } diff --git a/src/chocolatey/infrastructure.app/services/IRegistryService.cs b/src/chocolatey/infrastructure.app/services/IRegistryService.cs index af451bcc01..b29ed6c48f 100644 --- a/src/chocolatey/infrastructure.app/services/IRegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/IRegistryService.cs @@ -23,6 +23,6 @@ public interface IRegistryService Registry get_differences(Registry before, Registry after); void save_to_file(Registry snapshot, string filePath); Registry read_from_file(string filePath); - bool value_exists(string keyPath, string value); + bool installer_value_exists(string keyPath, string value); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index 812a751e83..51837e54f5 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -202,9 +202,8 @@ public void save_to_file(Registry snapshot, string filePath) _xmlService.serialize(snapshot, filePath); } - public bool value_exists(string keyPath, string value) + public bool installer_value_exists(string keyPath, string value) { - //todo: make this check less crazy... return get_installer_keys().RegistryKeys.Any(k => k.KeyPath == keyPath); } From 58d0a394393a3492c651e90b5328dc59e8ef097c Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 11:50:59 -0500 Subject: [PATCH 19/29] (GH-14) attempt exec minimized When the process creates a window anyway, attempt to run it minimized. --- src/chocolatey/infrastructure/commands/CommandExecutor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chocolatey/infrastructure/commands/CommandExecutor.cs b/src/chocolatey/infrastructure/commands/CommandExecutor.cs index 56a5cce761..05f6eb39d9 100644 --- a/src/chocolatey/infrastructure/commands/CommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/CommandExecutor.cs @@ -124,7 +124,8 @@ bool updateProcessPath WorkingDirectory = workingDirectory, RedirectStandardOutput = true, RedirectStandardError = true, - CreateNoWindow = true + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Minimized }; using (var p = initialize_process()) From f4895455e9aafc063cbd574467912d39a3b00acc Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 14:43:50 -0500 Subject: [PATCH 20/29] (refactor) IFileSystem.get_current_assembly_path Move all calls to `Assembly.GetExecutingAssembly().CodeBase` path to IFileSystem instead. The code to get the curent assembly path was in quite a few locations. This moves it down to one location. --- src/chocolatey.console/Program.cs | 2 +- src/chocolatey.tests.integration/NUnitSetup.cs | 2 +- src/chocolatey.tests.integration/Scenario.cs | 2 +- .../infrastructure/cryptography/CrytpoHashProviderSpecs.cs | 2 +- src/chocolatey/infrastructure.app/ApplicationParameters.cs | 6 +++--- .../commands/ChocolateyUnpackSelfCommand.cs | 2 +- src/chocolatey/infrastructure/commands/CommandExecutor.cs | 4 ++-- .../infrastructure/commands/PowershellExecutor.cs | 2 +- .../infrastructure/filesystem/DotNetFileSystem.cs | 5 +++++ src/chocolatey/infrastructure/filesystem/IFileSystem.cs | 6 ++++++ 10 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/chocolatey.console/Program.cs b/src/chocolatey.console/Program.cs index ab64b5bf0d..77458b5a9a 100644 --- a/src/chocolatey.console/Program.cs +++ b/src/chocolatey.console/Program.cs @@ -183,7 +183,7 @@ private static void remove_old_chocolatey_exe(IFileSystem fileSystem) { try { - fileSystem.delete_file(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty) + ".old"); + fileSystem.delete_file(fileSystem.get_current_assembly_path() + ".old"); fileSystem.delete_file(fileSystem.combine_paths(AppDomain.CurrentDomain.BaseDirectory, "choco.exe.old")); } catch (Exception ex) diff --git a/src/chocolatey.tests.integration/NUnitSetup.cs b/src/chocolatey.tests.integration/NUnitSetup.cs index 4b3aa1a965..f887003ed0 100644 --- a/src/chocolatey.tests.integration/NUnitSetup.cs +++ b/src/chocolatey.tests.integration/NUnitSetup.cs @@ -57,7 +57,7 @@ private static void fix_application_parameter_variables(Container container) { var fileSystem = container.GetInstance(); - var applicationLocation = fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + var applicationLocation = fileSystem.get_directory_name(fileSystem.get_current_assembly_path()); var field = typeof (ApplicationParameters).GetField("InstallLocation"); field.SetValue(null, applicationLocation); diff --git a/src/chocolatey.tests.integration/Scenario.cs b/src/chocolatey.tests.integration/Scenario.cs index 08fa4ae024..f96983da99 100644 --- a/src/chocolatey.tests.integration/Scenario.cs +++ b/src/chocolatey.tests.integration/Scenario.cs @@ -35,7 +35,7 @@ public class Scenario public static string get_top_level() { - return _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + return _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); } public static string get_package_install_path() diff --git a/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs index 20aa0947e7..60f822664a 100644 --- a/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs @@ -35,7 +35,7 @@ public override void Context() { FileSystem = new DotNetFileSystem(); Provider = new CrytpoHashProvider(FileSystem,CryptoHashProviderType.Md5); - ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), "context"); + ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(FileSystem.get_current_assembly_path()), "context"); } } diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index a546a87287..8b550c8573 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -30,12 +30,12 @@ public static class ApplicationParameters public static readonly string ChocolateyInstallEnvironmentVariableName = "ChocolateyInstall"; public static readonly string Name = "Chocolatey"; #if DEBUG - public static readonly string InstallLocation = _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + public static readonly string InstallLocation = _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); #else - public static readonly string InstallLocation = Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)); + public static readonly string InstallLocation = System.Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); #endif - public static readonly string CommonAppDataChocolatey = _fileSystem.combine_paths(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Name); + public static readonly string CommonAppDataChocolatey = _fileSystem.combine_paths(System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData), Name); public static readonly string LoggingLocation = _fileSystem.combine_paths(InstallLocation, "logs"); public static readonly string LoggingFile = @"chocolatey.log"; public static readonly string Log4NetConfigurationAssembly = @"chocolatey"; diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs index 49c257fc76..5b7b9e7a23 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUnpackSelfCommand.cs @@ -98,7 +98,7 @@ public void run(ChocolateyConfiguration configuration) AssemblyFileExtractor.extract_all_resources_to_relative_directory( _fileSystem, assembly, - _fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), + _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()), folders, ApplicationParameters.ChocolateyFileResources, overwriteExisting: configuration.Force, diff --git a/src/chocolatey/infrastructure/commands/CommandExecutor.cs b/src/chocolatey/infrastructure/commands/CommandExecutor.cs index 05f6eb39d9..fd9ed43c3a 100644 --- a/src/chocolatey/infrastructure/commands/CommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/CommandExecutor.cs @@ -49,7 +49,7 @@ public static void initialize_with(Lazy file_system, Func public int execute(string process, string arguments, int waitForExitInSeconds) { - return execute(process, arguments, waitForExitInSeconds, file_system.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty))); + return execute(process, arguments, waitForExitInSeconds, file_system.get_directory_name(file_system.get_current_assembly_path())); } public int execute( @@ -64,7 +64,7 @@ public int execute( return execute(process, arguments, waitForExitInSeconds, - file_system.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), + file_system.get_directory_name(file_system.get_current_assembly_path()), stdOutAction, stdErrAction, updateProcessPath diff --git a/src/chocolatey/infrastructure/commands/PowershellExecutor.cs b/src/chocolatey/infrastructure/commands/PowershellExecutor.cs index 1a630310aa..ef44dc84d9 100644 --- a/src/chocolatey/infrastructure/commands/PowershellExecutor.cs +++ b/src/chocolatey/infrastructure/commands/PowershellExecutor.cs @@ -50,7 +50,7 @@ Action stdErrAction _powershell, arguments, waitForExitSeconds, - workingDirectory: fileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), + workingDirectory: fileSystem.get_directory_name(fileSystem.get_current_assembly_path()), stdOutAction: stdOutAction, stdErrAction: stdErrAction, updateProcessPath: true diff --git a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs index f2dc91eaec..f78064b550 100644 --- a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs @@ -81,6 +81,11 @@ public char get_path_directory_separator_char() return Path.DirectorySeparatorChar; } + public string get_current_assembly_path() + { + return Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty); + } + #endregion #region File diff --git a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs index 959a9205c9..971990e475 100644 --- a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs @@ -53,6 +53,12 @@ public interface IFileSystem /// char get_path_directory_separator_char(); + ///

+ /// Gets the location of the executing assembly + /// + /// The path to the executing assembly + string get_current_assembly_path(); + #endregion #region File From 4bb5d92badfaccb57999c68202b3c3a53db3d472 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 14:45:20 -0500 Subject: [PATCH 21/29] (GH-14) use case insensitive dictionaries --- .../infrastructure.app/services/NugetService.cs | 8 ++++---- .../infrastructure.app/services/RubyGemsService.cs | 6 +++--- .../infrastructure.app/services/WebPiService.cs | 6 +++--- .../infrastructure.app/services/WindowsFeatureService.cs | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 8a4477c4c8..18eb81056d 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -86,7 +86,7 @@ public void list_noop(ChocolateyConfiguration config) public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults = true) { - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var packages = NugetList.GetPackages(config, _nugetLogger).ToList(); @@ -239,7 +239,7 @@ public void install_noop(ChocolateyConfiguration config, Action c public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { _fileSystem.create_directory_if_not_exists(ApplicationParameters.PackagesLocation); - var packageInstalls = new ConcurrentDictionary(); + var packageInstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); //todo: handle all @@ -404,7 +404,7 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, bool performAction) { _fileSystem.create_directory_if_not_exists(ApplicationParameters.PackagesLocation); - var packageInstalls = new ConcurrentDictionary(); + var packageInstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; var packageManager = NugetCommon.GetPackageManager( @@ -754,7 +754,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, bool performAction) { - var packageUninstalls = new ConcurrentDictionary(); + var packageUninstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; var packageManager = NugetCommon.GetPackageManager(config, _nugetLogger, diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs index bb922cd9c0..c2bf31b382 100644 --- a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -132,7 +132,7 @@ public void list_noop(ChocolateyConfiguration config) public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) { - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); Environment.ExitCode = _commandExecutor.execute( @@ -172,7 +172,7 @@ public void install_noop(ChocolateyConfiguration config, Action c public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) @@ -236,7 +236,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) { this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); - return new ConcurrentDictionary(); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 1f880e30c4..a04e2f027d 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -128,7 +128,7 @@ public void list_noop(ChocolateyConfiguration config) public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) { - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var args = ExternalCommandArgsBuilder.build_arguments(config, _listArguments); //var whereToStartRecording = "---"; @@ -185,7 +185,7 @@ public void install_noop(ChocolateyConfiguration config, Action c public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) { - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) @@ -242,7 +242,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) { this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); - return new ConcurrentDictionary(); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs index ce91cdd8ae..46b18cafef 100644 --- a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -184,7 +184,7 @@ public ConcurrentDictionary list_run(ChocolateyConfigurat { set_executable_path_if_not_set(); var args = build_args(config, _listArguments); - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); Environment.ExitCode = _commandExecutor.execute( _exePath, @@ -249,7 +249,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu { set_executable_path_if_not_set(); var args = build_args(config, _installArguments); - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { @@ -311,7 +311,7 @@ public ConcurrentDictionary upgrade_noop(ChocolateyConfig { set_executable_path_if_not_set(); this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); - return new ConcurrentDictionary(); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) @@ -331,7 +331,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi { set_executable_path_if_not_set(); var args = build_args(config, _uninstallArguments); - var packageResults = new ConcurrentDictionary(); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { From 31e3f9cdb905732a45d2436db1949bf53f65c73f Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 14:45:46 -0500 Subject: [PATCH 22/29] (doc) add returns statement --- src/chocolatey/infrastructure/filesystem/IFileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs index 971990e475..daae73d06b 100644 --- a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs @@ -245,7 +245,7 @@ public interface IFileSystem /// /// Gets the current working directory of the application. /// - /// + /// The path to the directory string get_current_directory(); /// From 0f6bc806bd1dea1f4c4df0fe814bd1e4944db66f Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 16:30:01 -0500 Subject: [PATCH 23/29] (GH-14) Find path to executable Provide a way to determine if a command / executable is on the file system. It starts by getting the path extensions `PATHEXT` environment variable and using those extensions combined with the executable name (unless an extension is provided as part of the name) and searching for the file in all path locations, starting at current directory and moving out from there in the order things would be searched for by the system. If nothing is found, we just return the name that was passed into the method. --- .../filesystem/DotNetFileSystemSpecs.cs | 48 +++++++ .../filesystem/DotNetFileSystemSpecs.cs | 135 ++++++++++++++++-- .../ApplicationParameters.cs | 7 + .../infrastructure/adapters/Environment.cs | 5 + .../infrastructure/adapters/IEnvironment.cs | 7 + .../filesystem/DotNetFileSystem.cs | 73 +++++++++- .../infrastructure/filesystem/IFileSystem.cs | 8 ++ 7 files changed, 267 insertions(+), 16 deletions(-) diff --git a/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs b/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs index 653fb137c3..1fcbdc4c4c 100644 --- a/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs @@ -21,6 +21,7 @@ namespace chocolatey.tests.integration.infrastructure.filesystem using NUnit.Framework; using Should; using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.platforms; public class DotNetFileSystemSpecs { @@ -48,6 +49,53 @@ public override void Context() } } + [Category("Integration")] + public class when_finding_paths_to_executables_with_dotNetFileSystem : DotNetFileSystemSpecsBase + { + public override void Because() + { + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable() + { + FileSystem.get_executable_path("cmd").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd" + ); + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable_with_extension() + { + FileSystem.get_executable_path("cmd.exe").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd" + ); + } + + [Fact] + public void GetExecutablePath_should_return_same_value_when_executable_is_not_found() + { + FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea"); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_null() + { + FileSystem.get_executable_path(null).ShouldEqual(string.Empty); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string() + { + FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty); + } + + } + [Category("Integration")] public class when_doing_file_system_operations_with_dotNetFileSystem : DotNetFileSystemSpecsBase { diff --git a/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs b/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs index f00fb5b6d0..fd3c1b8a17 100644 --- a/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs +++ b/src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs @@ -17,7 +17,10 @@ namespace chocolatey.tests.infrastructure.filesystem { using System; using System.IO; + using Moq; using Should; + using chocolatey.infrastructure.adapters; + using chocolatey.infrastructure.app; using chocolatey.infrastructure.filesystem; using chocolatey.infrastructure.platforms; @@ -73,8 +76,8 @@ public void GetExtension_should_return_the_extension_of_the_filename_even_with_a public void GetDirectoryName_should_return_the_directory_of_the_path_to_the_file() { FileSystem.get_directory_name("C:\\temp\\test.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp" : "C:/temp"); } @@ -82,8 +85,8 @@ public void GetDirectoryName_should_return_the_directory_of_the_path_to_the_file public void Combine_should_combine_the_file_paths_of_all_the_included_items_together() { FileSystem.combine_paths("C:\\temp", "yo", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\filename.txt" : "C:/temp/yo/filename.txt"); } @@ -91,8 +94,8 @@ public void Combine_should_combine_the_file_paths_of_all_the_included_items_toge public void Combine_should_combine_when_paths_have_backslashes_in_subpaths() { FileSystem.combine_paths("C:\\temp", "yo\\timmy", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\timmy\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\timmy\\filename.txt" : "C:/temp/yo/timmy/filename.txt"); } @@ -100,19 +103,129 @@ public void Combine_should_combine_when_paths_have_backslashes_in_subpaths() public void Combine_should_combine_when_paths_start_with_backslashes_in_subpaths() { FileSystem.combine_paths("C:\\temp", "\\yo", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\filename.txt" : "C:/temp/yo/filename.txt"); } - + [Fact] public void Combine_should_combine_when_paths_start_with_forwardslashes_in_subpaths() { FileSystem.combine_paths("C:\\temp", "/yo", "filename.txt").ShouldEqual( - Platform.get_platform() == PlatformType.Windows ? - "C:\\temp\\yo\\filename.txt" + Platform.get_platform() == PlatformType.Windows ? + "C:\\temp\\yo\\filename.txt" : "C:/temp/yo/filename.txt"); } } + + public class when_finding_paths_to_executables_with_dotNetFileSystem : DotNetFileSystemSpecsBase + { + public Mock _environment = new Mock(); + + public override void Context() + { + base.Context(); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions)).Returns(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL"); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.Path)).Returns( + @"C:\ProgramData\Chocolatey\bin{0}C:\Program Files\Microsoft\Web Platform Installer\{0}C:\Users\yes\AppData\Roaming\Boxstarter{0}C:\tools\ChocolateyPackageUpdater{0}C:\Windows\system32{0}C:\Windows{0}C:\Windows\System32\Wbem{0}C:\Windows\System32\WindowsPowerShell\v1.0\{0}" + .format_with(Path.PathSeparator) + ); + FileSystem.initialize_with(new Lazy(() => _environment.Object)); + } + + public override void Because() + { + } + + private void reset() + { + _environment.ResetCalls(); + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable() + { + FileSystem.get_executable_path("cmd").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd" + ); + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable_with_extension() + { + FileSystem.get_executable_path("cmd.exe").ShouldEqual( + Platform.get_platform() == PlatformType.Windows ? + "C:\\Windows\\system32\\cmd.exe" + : "cmd.exe" + ); + } + + [Fact] + public void GetExecutablePath_should_return_same_value_when_executable_is_not_found() + { + FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea"); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_null() + { + FileSystem.get_executable_path(null).ShouldEqual(string.Empty); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string() + { + FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty); + } + } + + public class when_finding_paths_to_executables_with_dotNetFileSystem_with_empty_path_extensions : DotNetFileSystemSpecsBase + { + public Mock _environment = new Mock(); + + public override void Context() + { + base.Context(); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions)).Returns(string.Empty); + _environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.Path)).Returns( + "/usr/local/bin{0}/usr/bin/{0}/bin{0}/usr/sbin{0}/sbin" + .format_with(Path.PathSeparator) + ); + FileSystem.initialize_with(new Lazy(() => _environment.Object)); + } + + public override void Because() + { + } + + [Fact] + public void GetExecutablePath_should_find_existing_executable() + { + FileSystem.get_executable_path("ls").ShouldEqual( + Platform.get_platform() != PlatformType.Windows ? + "/bin/ls" + : "ls"); + } + + [Fact] + public void GetExecutablePath_should_return_same_value_when_executable_is_not_found() + { + FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea"); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_null() + { + FileSystem.get_executable_path(null).ShouldEqual(string.Empty); + } + + [Fact] + public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string() + { + FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty); + } + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 8b550c8573..e635f880de 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -59,6 +59,13 @@ public static class ApplicationParameters public static readonly string RegistryValueInstallLocation = "InstallLocation"; public static readonly string AllPackages = "all"; + public static class Environment + { + public static readonly string Path = "Path"; + public static readonly string PathExtensions = "PATHEXT"; + public static readonly string PathExtensionsSeparator = ";"; + } + /// /// Default is 45 minutes /// diff --git a/src/chocolatey/infrastructure/adapters/Environment.cs b/src/chocolatey/infrastructure/adapters/Environment.cs index 5013454ccb..be5b5fca87 100644 --- a/src/chocolatey/infrastructure/adapters/Environment.cs +++ b/src/chocolatey/infrastructure/adapters/Environment.cs @@ -38,5 +38,10 @@ public string NewLine { get { return System.Environment.NewLine; } } + + public string GetEnvironmentVariable(string variable) + { + return System.Environment.GetEnvironmentVariable(variable); + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure/adapters/IEnvironment.cs b/src/chocolatey/infrastructure/adapters/IEnvironment.cs index 476868b6cf..60549199a7 100644 --- a/src/chocolatey/infrastructure/adapters/IEnvironment.cs +++ b/src/chocolatey/infrastructure/adapters/IEnvironment.cs @@ -62,6 +62,13 @@ public interface IEnvironment /// /// 1 string NewLine { get; } + + /// + /// Gets the environment variable. + /// + /// The variable. + /// + string GetEnvironmentVariable(string variable); } // ReSharper restore InconsistentNaming diff --git a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs index f78064b550..4184662d86 100644 --- a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs @@ -17,14 +17,19 @@ namespace chocolatey.infrastructure.filesystem { using System; using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; + using adapters; + using app; using platforms; using tolerance; + using Assembly = adapters.Assembly; + using Environment = adapters.Environment; /// /// Implementation of IFileSystem for Dot Net @@ -33,14 +38,26 @@ namespace chocolatey.infrastructure.filesystem public sealed class DotNetFileSystem : IFileSystem { private readonly int TIMES_TO_TRY_OPERATION = 3; + private static Lazy environment_initializer = new Lazy(() => new Environment()); private void allow_retries(Action action) { FaultTolerance.retry( - TIMES_TO_TRY_OPERATION, - action, - waitDurationMilliseconds: 200, - increaseRetryByMilliseconds: 100); + TIMES_TO_TRY_OPERATION, + action, + waitDurationMilliseconds: 200, + increaseRetryByMilliseconds: 100); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void initialize_with(Lazy environment) + { + environment_initializer = environment; + } + + private static IEnvironment Environment + { + get { return environment_initializer.Value; } } #region Path @@ -81,6 +98,52 @@ public char get_path_directory_separator_char() return Path.DirectorySeparatorChar; } + public char get_path_separator() + { + return Path.PathSeparator; + } + + public string get_executable_path(string executableName) + { + if (string.IsNullOrWhiteSpace(executableName)) return string.Empty; + + var isWindows = Platform.get_platform() == PlatformType.Windows; + IList extensions = new List(); + + if (get_file_name_without_extension(executableName).is_equal_to(executableName) && isWindows) + { + var pathExtensions = Environment.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions).to_string().Split(new[] {ApplicationParameters.Environment.PathExtensionsSeparator}, StringSplitOptions.RemoveEmptyEntries); + foreach (var extension in pathExtensions.or_empty_list_if_null()) + { + extensions.Add(extension.StartsWith(".") ? extension : ".{0}".format_with(extension)); + } + } + + // Always add empty, for when the executable name is enough. + extensions.Add(string.Empty); + + // Gets the path to an executable based on looking in current + // working directory, next to the running process, then among the + // derivatives of Path and Pathext variables, applied in order. + var searchPaths = new List(); + searchPaths.Add(get_current_directory()); + searchPaths.Add(get_directory_name(get_current_assembly_path())); + searchPaths.AddRange(Environment.GetEnvironmentVariable(ApplicationParameters.Environment.Path).to_string().Split(new[] { get_path_separator() }, StringSplitOptions.RemoveEmptyEntries)); + + foreach (var path in searchPaths.or_empty_list_if_null()) + { + foreach (var extension in extensions.or_empty_list_if_null()) + { + var possiblePath = combine_paths(path, "{0}{1}".format_with(executableName, extension.to_lower())); + if (file_exists(possiblePath)) return possiblePath; + } + } + + // If not found, return the same as passed in - it may work, + // but possibly not. + return executableName; + } + public string get_current_assembly_path() { return Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty); @@ -130,7 +193,7 @@ public FileInfo get_file_info_for(string filePath) return new FileInfo(filePath); } - public DateTime get_file_modified_date(string filePath) + public System.DateTime get_file_modified_date(string filePath) { return new FileInfo(filePath).LastWriteTime; } diff --git a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs index daae73d06b..f7b5613481 100644 --- a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs @@ -53,6 +53,14 @@ public interface IFileSystem /// char get_path_directory_separator_char(); + /// + /// Gets the path to an executable based on looking in current directory, next to the running process, then among the derivatives of Path and Pathext variables + /// + /// Name of the executable. + /// Based loosely on http://stackoverflow.com/a/5471032/18475 + /// + string get_executable_path(string executableName); + /// /// Gets the location of the executing assembly /// From b09644e00c925d6c17ad01a578fb726d07ccb4a4 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 16:38:58 -0500 Subject: [PATCH 24/29] (maint) formatting --- .../infrastructure.app/registration/ContainerBinding.cs | 2 +- src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 25ef8839fa..bf47635cf3 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -87,7 +87,7 @@ public void RegisterComponents(Container container) }; return list.AsReadOnly(); }, Lifestyle.Singleton); - + container.Register>(() => { var list = new List diff --git a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs index 4184662d86..8d636b4822 100644 --- a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs @@ -157,11 +157,11 @@ public IEnumerable get_files(string directoryPath, string pattern = "*.* { return Directory.EnumerateFiles(directoryPath, pattern, option); } - + public IEnumerable get_files(string directoryPath, string[] extensions, SearchOption option = SearchOption.TopDirectoryOnly) { return Directory.EnumerateFiles(directoryPath, "*.*", option) - .Where(f => extensions.Any(x => f.EndsWith(x,StringComparison.OrdinalIgnoreCase))); + .Where(f => extensions.Any(x => f.EndsWith(x, StringComparison.OrdinalIgnoreCase))); } public bool file_exists(string filePath) From 00d35b19d7dd458b5bedd872ef5206195f4b7156 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 16:52:25 -0500 Subject: [PATCH 25/29] (GH-14) RegistryService.get_key() Get registry keys and values from the registry. --- src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../services/RegistryServiceSpecs.cs | 150 ++++++++++++++++++ .../services/IRegistryService.cs | 4 +- .../services/RegistryService.cs | 25 +++ 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 9033d98df5..2d2e98c5ef 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -80,6 +80,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs new file mode 100644 index 0000000000..5d4bdda5a1 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/RegistryServiceSpecs.cs @@ -0,0 +1,150 @@ +// Copyright © 2011 - Present 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.tests.infrastructure.app.services +{ + using System; + using Microsoft.Win32; + using Moq; + using Should; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.services; + using Registry = chocolatey.infrastructure.app.domain.Registry; + + public class RegistryServiceSpecs + { + public abstract class RegistryServiceSpecsBase : TinySpec + { + protected RegistryService Service; + protected Mock FileSystem = new Mock(); + protected Mock XmlService = new Mock(); + + public override void Context() + { + reset(); + Service = new RegistryService(XmlService.Object, FileSystem.Object); + } + + protected void reset() + { + FileSystem.ResetCalls(); + XmlService.ResetCalls(); + MockLogger.reset(); + } + } + + + + public class when_RegistryService_get_installer_keys_is_called : RegistryServiceSpecsBase + { + private Registry _result; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + _result = Service.get_installer_keys(); + } + + + [Fact] + public void should_not_be_null() + { + _result.ShouldNotBeNull(); + } + + } + + public class when_RegistryService_get_key_is_called_for_a_value_that_exists : RegistryServiceSpecsBase + { + //todo does any of this test correctly on *nix? + + private RegistryKey _result; + private RegistryHive _hive = RegistryHive.CurrentUser; + private string _subkeyPath = "Console"; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + _result = Service.get_key(_hive, _subkeyPath); + } + + [Fact] + public void should_return_a_non_null_value() + { + _result.ShouldNotBeNull(); + } + + [Fact] + public void should_return_a_value_of_type_RegistryKey() + { + _result.ShouldBeType(); + } + + [Fact] + public void should_contain_keys() + { + _result.GetSubKeyNames().ShouldNotBeEmpty(); + } + + [Fact] + public void should_contain_values() + { + _result.GetValueNames().ShouldNotBeEmpty(); + } + } + + public class when_RegistryService_get_key_is_called_for_a_value_that_does_not_exist : RegistryServiceSpecsBase + { + //todo does any of this test correctly on *nix? + + private RegistryKey _result; + private RegistryHive _hive = RegistryHive.CurrentUser; + private string _subkeyPath = "Software\\alsdjfalskjfaasdfasdf"; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + _result = Service.get_key(_hive, _subkeyPath); + } + + [Fact] + public void should_not_error() + { + //nothing to see here + } + + [Fact] + public void should_return_null_key() + { + _result.ShouldBeNull(); + } + } + + //todo a subkey that exists only in 32 bit mode (must create it to test it though) + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/IRegistryService.cs b/src/chocolatey/infrastructure.app/services/IRegistryService.cs index b29ed6c48f..47adab543a 100644 --- a/src/chocolatey/infrastructure.app/services/IRegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/IRegistryService.cs @@ -15,7 +15,8 @@ namespace chocolatey.infrastructure.app.services { - using domain; + using Microsoft.Win32; + using Registry = domain.Registry; public interface IRegistryService { @@ -24,5 +25,6 @@ public interface IRegistryService void save_to_file(Registry snapshot, string filePath); Registry read_from_file(string filePath); bool installer_value_exists(string keyPath, string value); + RegistryKey get_key(RegistryHive hive, string subKeyPath); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index 51837e54f5..b49c1db7ec 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -216,5 +216,30 @@ public Registry read_from_file(string filePath) return _xmlService.deserialize(filePath); } + + public RegistryKey get_key(RegistryHive hive, string subKeyPath) + { + IList keyLocations = new List(); + if (Environment.Is64BitOperatingSystem) + { + keyLocations.Add(RegistryKey.OpenBaseKey(hive, RegistryView.Registry64)); + } + + keyLocations.Add(RegistryKey.OpenBaseKey(hive, RegistryView.Registry32)); + + foreach (var topLevelRegistryKey in keyLocations) + { + using (topLevelRegistryKey) + { + var key = topLevelRegistryKey.OpenSubKey(subKeyPath, RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey); + if (key != null) + { + return key; + } + } + } + + return null; + } } } \ No newline at end of file From c133229c709cfcb400e1f3c0071c92302448ab00 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jun 2015 16:56:30 -0500 Subject: [PATCH 26/29] (GH-14) Cygwin Alternate source Provide the ability to install with cygwin, but not much else. The executable unfortunately doesn't also execute without popping a window, which is very unfortunate. I will probably look at using the cyg-get wrapper instead. --- src/chocolatey/chocolatey.csproj | 1 + .../registration/ContainerBinding.cs | 7 +- .../services/CygwinService.cs | 290 ++++++++++++++++++ 3 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 src/chocolatey/infrastructure.app/services/CygwinService.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index ba82ad0c75..52069d46ad 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -107,6 +107,7 @@ + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index bf47635cf3..a5cdb31b06 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -93,9 +93,10 @@ public void RegisterComponents(Container container) var list = new List { container.GetInstance(), - new WebPiService(container.GetInstance(),container.GetInstance()), - new WindowsFeatureService(container.GetInstance(),container.GetInstance(),container.GetInstance()), - new RubyGemsService(container.GetInstance(),container.GetInstance()) + new WebPiService(container.GetInstance(), container.GetInstance()), + new WindowsFeatureService(container.GetInstance(), container.GetInstance(), container.GetInstance()), + new CygwinService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()), + new RubyGemsService(container.GetInstance(), container.GetInstance()) }; return list.AsReadOnly(); }, Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure.app/services/CygwinService.cs b/src/chocolatey/infrastructure.app/services/CygwinService.cs new file mode 100644 index 0000000000..bb393519c0 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/CygwinService.cs @@ -0,0 +1,290 @@ +// Copyright © 2011 - Present 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.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Text.RegularExpressions; + using Microsoft.Win32; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using results; + + /// + /// Alternative Source for Cygwin + /// + /// + /// https://cygwin.com/faq/faq.html#faq.setup.cli + /// + public sealed class CygwinService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private readonly IRegistryService _registryService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string INSTALL_ROOT_TOKEN = "{{installroot}}"; + public const string CYGWIN_PACKAGE = "cygwin"; + private string _rootDirectory = string.Empty; + + private const string APP_NAME = "Cygwin"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"Extracting from file", RegexOptions.Compiled); + public static readonly Regex PackageNameRegex = new Regex(@"/(?<{0}>[^/]*).tar.".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public CygwinService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem, IRegistryService registryService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + _registryService = registryService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_install_dictionary(_installArguments); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + //args.Add("_cmd_c_", new ExternalCommandArgument { ArgumentOption = "/c", Required = true }); + //args.Add("_app_", new ExternalCommandArgument + //{ + // ArgumentOption = "", + // ArgumentValue = _fileSystem.combine_paths(INSTALL_ROOT_TOKEN, "cygwinsetup.exe"), + // QuoteValue = false, + // UseValueOnly = true, + // Required = true + //}); + args.Add("_quiet_", new ExternalCommandArgument {ArgumentOption = "--quiet-mode", Required = true}); + args.Add("_no_desktop_", new ExternalCommandArgument {ArgumentOption = "--no-desktop", Required = true}); + args.Add("_no_startmenu_", new ExternalCommandArgument {ArgumentOption = "--no-startmenu", Required = true}); + args.Add("_root_", new ExternalCommandArgument + { + ArgumentOption = "--root ", + ArgumentValue = INSTALL_ROOT_TOKEN, + QuoteValue = false, + Required = true + }); + args.Add("_local_pkgs_dir_", new ExternalCommandArgument + { + ArgumentOption = "--local-package-dir ", + ArgumentValue = "{0}\\packages".format_with(INSTALL_ROOT_TOKEN), + QuoteValue = false, + Required = true + }); + + args.Add("_site_", new ExternalCommandArgument + { + ArgumentOption = "--site ", + ArgumentValue = "http://mirrors.kernel.org/sourceware/cygwin/", + QuoteValue = false, + Required = true + }); + + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "--packages ", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + Required = true + }); + } + + public SourceType SourceType + { + get { return SourceType.cygwin; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + var runnerConfig = new ChocolateyConfiguration + { + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig, logResults: false); + + if (!localPackages.ContainsKey(CYGWIN_PACKAGE)) + { + runnerConfig.PackageNames = CYGWIN_PACKAGE; + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + + set_root_dir_if_not_set(); + } + + public void set_root_dir_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_rootDirectory)) return; + + var setupKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Cygwin\\setup"); + if (setupKey != null) + { + _rootDirectory = setupKey.GetValue("rootdir", string.Empty).to_string(); + } + + if (string.IsNullOrWhiteSpace(_rootDirectory)) + { + var binRoot = Environment.GetEnvironmentVariable("ChocolateyBinRoot"); + if (string.IsNullOrWhiteSpace(binRoot)) binRoot = "c:\\tools"; + + _rootDirectory = _fileSystem.combine_paths(binRoot,"cygwin"); + } + } + + public string get_exe(string rootpath) + { + return _fileSystem.combine_paths(rootpath, "cygwinsetup.exe"); + } + + public void list_noop(ChocolateyConfiguration config) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement list".format_with(APP_NAME)); + } + + public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) + { + throw new NotImplementedException("{0} does not implement list".format_with(APP_NAME)); + } + + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + args = args.Replace(INSTALL_ROOT_TOKEN, _rootDirectory); + + return args; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(get_exe(_rootDirectory), args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + + var exitCode = _commandExecutor.execute( + get_exe(_rootDirectory), + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement upgrade".format_with(APP_NAME)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file From 66e98ea4679e3079694b3e0fd8329c01cc786e02 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 4 Jun 2015 12:23:20 -0500 Subject: [PATCH 27/29] (maint) add registry service evaluation --- .../services/RegistryService.cs | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index b49c1db7ec..5dee9bb8c8 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -34,6 +34,7 @@ public sealed class RegistryService : IRegistryService { private readonly IXmlService _xmlService; private readonly IFileSystem _fileSystem; + private readonly bool _logOutput = false; public RegistryService(IXmlService xmlService, IFileSystem fileSystem) { @@ -69,13 +70,16 @@ public Registry get_installer_keys() registryKey.Dispose(); } - //Console.WriteLine(""); - //Console.WriteLine("A total of {0} unrecognized apps".format_with(snapshot.RegistryKeys.Where((p) => p.InstallerType == InstallerType.Unknown).Count())); - //Console.WriteLine(""); + if (_logOutput) + { + Console.WriteLine(""); + Console.WriteLine("A total of {0} unrecognized apps".format_with(snapshot.RegistryKeys.Where((p) => p.InstallerType == InstallerType.Unknown && p.is_in_programs_and_features()).Count())); + Console.WriteLine(""); - //Console.WriteLine(""); - //Console.WriteLine("A total of {0} of {1} are programs and features apps".format_with(snapshot.RegistryKeys.Where((p) => p.is_in_programs_and_features()).Count(), snapshot.RegistryKeys.Count)); - //Console.WriteLine(""); + Console.WriteLine(""); + Console.WriteLine("A total of {0} of {1} are programs and features apps".format_with(snapshot.RegistryKeys.Where((p) => p.is_in_programs_and_features()).Count(), snapshot.RegistryKeys.Count)); + Console.WriteLine(""); + } return snapshot; } @@ -168,21 +172,23 @@ public void evaluate_keys(RegistryKey key, Registry snapshot) appKey.InstallerType = InstallerType.Custom; } - //if (appKey.InstallerType == InstallerType.Msi) - //{ - //Console.WriteLine(""); - //if (!string.IsNullOrWhiteSpace(appKey.UninstallString)) - //{ - // Console.WriteLine(appKey.UninstallString.to_string().Split(new[] { " /", " -" }, StringSplitOptions.RemoveEmptyEntries)[0]); - // key.UninstallString.to_string().Split(new[] { " /", " -" }, StringSplitOptions.RemoveEmptyEntries); - //} - //foreach (var name in key.GetValueNames()) - //{ - // var kind = key.GetValueKind(name); - // var value = key.GetValue(name); - // Console.WriteLine("key - {0}, name - {1}, kind - {2}, value - {3}".format_with(key.Name, name, kind, value.to_string())); - //} - //} + if (_logOutput) + { + if (appKey.is_in_programs_and_features() && appKey.InstallerType == InstallerType.Unknown) + { + foreach (var name in key.GetValueNames()) + { + //var kind = key.GetValueKind(name); + var value = key.GetValue(name); + if (name.is_equal_to("QuietUninstallString") || name.is_equal_to("UninstallString")) + { + Console.WriteLine("key - {0}|{1}={2}|Type detected={3}".format_with(key.Name, name, value.to_string(), appKey.InstallerType.to_string())); + } + + //Console.WriteLine("key - {0}, name - {1}, kind - {2}, value - {3}".format_with(key.Name, name, kind, value.to_string())); + } + } + } snapshot.RegistryKeys.Add(appKey); } From 60c99cb9ac6532455f49112fd6780e8bfcced3de Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 4 Jun 2015 16:32:27 -0500 Subject: [PATCH 28/29] (GH-14) use the packagenames separator --- src/chocolatey/infrastructure.app/services/CygwinService.cs | 2 +- src/chocolatey/infrastructure.app/services/RubyGemsService.cs | 2 +- src/chocolatey/infrastructure.app/services/WebPiService.cs | 2 +- .../infrastructure.app/services/WindowsFeatureService.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/CygwinService.cs b/src/chocolatey/infrastructure.app/services/CygwinService.cs index bb393519c0..2b20a8bc6a 100644 --- a/src/chocolatey/infrastructure.app/services/CygwinService.cs +++ b/src/chocolatey/infrastructure.app/services/CygwinService.cs @@ -208,7 +208,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu var args = build_args(config, _installArguments); var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs index c2bf31b382..7f07c1008d 100644 --- a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -175,7 +175,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); - foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); var exitCode = _commandExecutor.execute( diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index a04e2f027d..61b56f341f 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -188,7 +188,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); var args = ExternalCommandArgsBuilder.build_arguments(config, _installArguments); - foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); var exitCode = _commandExecutor.execute( diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs index 46b18cafef..6e5b79f679 100644 --- a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -251,7 +251,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu var args = build_args(config, _installArguments); var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) { var packageName = packageToInstall; var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); @@ -333,7 +333,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi var args = build_args(config, _uninstallArguments); var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var packageToInstall in config.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) { var packageName = packageToInstall; var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageName, null, null)); From 45ed0657ade7f2d9c3ec6eb81257c95942409fb7 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Thu, 4 Jun 2015 17:35:02 -0500 Subject: [PATCH 29/29] (GH-14) Python Alternate Source - Able to find pip, starting by finding python, then going to the registry to find python install location. - Able to install python if not found - Able to list packages - Able to install pacakges - Able to upgrade packages - Able to uninstall packages --- src/chocolatey/chocolatey.csproj | 1 + .../registration/ContainerBinding.cs | 1 + .../services/PythonService.cs | 531 ++++++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/chocolatey/infrastructure.app/services/PythonService.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 52069d46ad..1b113a04fb 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -111,6 +111,7 @@ + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index a5cdb31b06..e1acad8400 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -96,6 +96,7 @@ public void RegisterComponents(Container container) new WebPiService(container.GetInstance(), container.GetInstance()), new WindowsFeatureService(container.GetInstance(), container.GetInstance(), container.GetInstance()), new CygwinService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()), + new PythonService(container.GetInstance(), container.GetInstance(), container.GetInstance(), container.GetInstance()), new RubyGemsService(container.GetInstance(), container.GetInstance()) }; return list.AsReadOnly(); diff --git a/src/chocolatey/infrastructure.app/services/PythonService.cs b/src/chocolatey/infrastructure.app/services/PythonService.cs new file mode 100644 index 0000000000..963ec5c39a --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/PythonService.cs @@ -0,0 +1,531 @@ +namespace chocolatey.infrastructure.app.services +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using Microsoft.Win32; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using logging; + using results; + + /// + /// Alternative Source for Installing Python packages + /// + public sealed class PythonService : ISourceRunner + { + private readonly ICommandExecutor _commandExecutor; + private readonly INugetService _nugetService; + private readonly IFileSystem _fileSystem; + private readonly IRegistryService _registryService; + private const string PACKAGE_NAME_TOKEN = "{{packagename}}"; + private const string LOG_LEVEL_TOKEN = "{{loglevel}}"; + private const string FORCE_TOKEN = "{{force}}"; + public const string PYTHON_PACKAGE = "python"; + private string _exePath = string.Empty; + + private const string APP_NAME = "Python"; + public const string PACKAGE_NAME_GROUP = "PkgName"; + public static readonly Regex InstalledRegex = new Regex(@"Successfully installed", RegexOptions.Compiled); + public static readonly Regex UninstalledRegex = new Regex(@"Successfully uninstalled", RegexOptions.Compiled); + public static readonly Regex PackageNameRegex = new Regex(@"\s(?<{0}>[^-\s]*)-".format_with(PACKAGE_NAME_GROUP), RegexOptions.Compiled); + public static readonly Regex ErrorRegex = new Regex(@"Error:", RegexOptions.Compiled); + public static readonly Regex ErrorNotFoundRegex = new Regex(@"Could not find any downloads that", RegexOptions.Compiled); + + private readonly IDictionary _listArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _installArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _upgradeArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary _uninstallArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public PythonService(ICommandExecutor commandExecutor, INugetService nugetService, IFileSystem fileSystem, IRegistryService registryService) + { + _commandExecutor = commandExecutor; + _nugetService = nugetService; + _fileSystem = fileSystem; + _registryService = registryService; + set_cmd_args_dictionaries(); + } + + /// + /// Set any command arguments dictionaries necessary for the service + /// + private void set_cmd_args_dictionaries() + { + set_list_dictionary(_listArguments); + set_install_dictionary(_installArguments); + set_upgrade_dictionary(_upgradeArguments); + set_uninstall_dictionary(_uninstallArguments); + } + + /// + /// Sets list dictionary + /// + private void set_list_dictionary(IDictionary args) + { + set_common_args(args); + args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "list", Required = true }); + } + + /// + /// Sets install dictionary + /// + private void set_install_dictionary(IDictionary args) + { + set_common_args(args); + + args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "install", Required = true }); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + } + + /// + /// Sets install dictionary + /// + private void set_upgrade_dictionary(IDictionary args) + { + set_common_args(args); + + args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "install", Required = true }); + args.Add("_upgrade_", new ExternalCommandArgument { ArgumentOption = "--upgrade", Required = true }); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + } + + /// + /// Sets uninstall dictionary + /// + private void set_uninstall_dictionary(IDictionary args) + { + set_common_args(args); + + args.Add("_command_", new ExternalCommandArgument { ArgumentOption = "uninstall", Required = true }); + args.Add("_confirm_", new ExternalCommandArgument { ArgumentOption = "-y", Required = true }); + args.Add("_package_name_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = PACKAGE_NAME_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + } + + private void set_common_args(IDictionary args) + { + args.Add("_loglevel_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = LOG_LEVEL_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + + args.Add("_force_", new ExternalCommandArgument + { + ArgumentOption = "", + ArgumentValue = FORCE_TOKEN, + QuoteValue = false, + UseValueOnly = true, + Required = true + }); + + + } + + public SourceType SourceType + { + get { return SourceType.python; } + } + + public void ensure_source_app_installed(ChocolateyConfiguration config, Action ensureAction) + { + //ensure at least python 2.7.9 is installed + var python = _fileSystem.get_executable_path("python"); + //python -V + + if (python.is_equal_to("python")) + { + var runnerConfig = new ChocolateyConfiguration + { + Sources = ApplicationParameters.PackagesLocation, + Debug = config.Debug, + Force = config.Force, + Verbose = config.Verbose, + CommandExecutionTimeoutSeconds = config.CommandExecutionTimeoutSeconds, + CacheLocation = config.CacheLocation, + RegularOutput = config.RegularOutput, + PromptForConfirmation = false, + AcceptLicense = true, + }; + runnerConfig.ListCommand.LocalOnly = true; + + var localPackages = _nugetService.list_run(runnerConfig, logResults: false); + + if (!localPackages.ContainsKey(PYTHON_PACKAGE)) + { + runnerConfig.PackageNames = PYTHON_PACKAGE; + runnerConfig.Sources = ApplicationParameters.ChocolateyCommunityFeedSource; + + var prompt = config.PromptForConfirmation; + config.PromptForConfirmation = false; + _nugetService.install_run(runnerConfig, ensureAction.Invoke); + config.PromptForConfirmation = prompt; + } + } + } + + public void set_executable_path_if_not_set() + { + if (!string.IsNullOrWhiteSpace(_exePath)) return; + + var python = _fileSystem.get_executable_path("python"); + + var pipPath = string.Empty; + if (!python.is_equal_to("python")) + { + pipPath = _fileSystem.combine_paths(_fileSystem.get_directory_name(python), "Scripts", "pip.exe"); + if (_fileSystem.file_exists(pipPath)) + { + _exePath = pipPath; + return; + } + } + + var topLevelPath = string.Empty; + var python34PathKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Python\\PythonCore\\3.4\\InstallPath"); + if (python34PathKey != null) + { + topLevelPath = python34PathKey.GetValue("", string.Empty).to_string(); + } + if (string.IsNullOrWhiteSpace(topLevelPath)) + { + var python27PathKey = _registryService.get_key(RegistryHive.LocalMachine, "SOFTWARE\\Python\\PythonCore\\2.7\\InstallPath"); + if (python27PathKey != null) + { + topLevelPath = python27PathKey.GetValue("", string.Empty).to_string(); + } + } + + if (string.IsNullOrWhiteSpace(topLevelPath)) + { + var binRoot = Environment.GetEnvironmentVariable("ChocolateyBinRoot"); + if (string.IsNullOrWhiteSpace(binRoot)) binRoot = "c:\\tools"; + + topLevelPath = _fileSystem.combine_paths(binRoot, "python"); + } + + pipPath = _fileSystem.combine_paths(_fileSystem.get_directory_name(topLevelPath), "Scripts", "pip.exe"); + if (_fileSystem.file_exists(pipPath)) + { + _exePath = pipPath; + } + + if (string.IsNullOrWhiteSpace(_exePath)) throw new FileNotFoundException("Unable to find pip"); + } + + public string build_args(ChocolateyConfiguration config, IDictionary argsDictionary) + { + var args = ExternalCommandArgsBuilder.build_arguments(config, argsDictionary); + + args = args.Replace(LOG_LEVEL_TOKEN, config.Debug ? "-vvv" : ""); + + if (config.CommandName.is_equal_to("intall")) + { + args = args.Replace(FORCE_TOKEN, config.Force ? "--ignore-installed" : ""); + } + else if (config.CommandName.is_equal_to("upgrade")) + { + args = args.Replace(FORCE_TOKEN, config.Force ? "--force-reinstall" : ""); + } + else + { + args = args.Replace(FORCE_TOKEN, ""); + } + + return args; + } + + public void list_noop(ChocolateyConfiguration config) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary list_run(ChocolateyConfiguration config, bool logResults) + { + set_executable_path_if_not_set(); + var args = build_args(config, _listArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + Environment.ExitCode = _commandExecutor.execute( + _exePath, + args, + config.CommandExecutionTimeoutSeconds, + stdOutAction: (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + if (logResults) + { + this.Log().Info(e.Data); + } + else + { + this.Log().Debug(() => "[{0}] {1}".format_with(APP_NAME, logMessage)); + } + }, + stdErrAction: (s, e) => + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => "{0}".format_with(e.Data)); + }, + updateProcessPath: false + ); + + return packageResults; + } + + public void install_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary install_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _installArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var pkgName = packageToInstall; + if (!string.IsNullOrWhiteSpace(config.Version)) + { + pkgName = "{0}=={1}".format_with(packageToInstall, config.Version); + } + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, pkgName); + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public ConcurrentDictionary upgrade_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _upgradeArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _upgradeArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var pkgName = packageToInstall; + if (!string.IsNullOrWhiteSpace(config.Version)) + { + pkgName = "{0}=={1}".format_with(packageToInstall, config.Version); + } + + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, pkgName); + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + + if (InstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been installed successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + public void uninstall_noop(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + this.Log().Info("Would have run '{0} {1}'".format_with(_exePath, args)); + } + + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + { + set_executable_path_if_not_set(); + var args = build_args(config, _uninstallArguments); + var packageResults = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + foreach (var packageToInstall in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries)) + { + var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); + + var exitCode = _commandExecutor.execute( + _exePath, + argsForPackage, + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Info(() => " [{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, packageToInstall)); + } + + if (UninstalledRegex.IsMatch(logMessage)) + { + var packageName = get_value_from_output(logMessage, PackageNameRegex, PACKAGE_NAME_GROUP); + var results = packageResults.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Note, packageName)); + this.Log().Info(ChocolateyLoggers.Important, " {0} has been uninstalled successfully.".format_with(string.IsNullOrWhiteSpace(packageName) ? packageToInstall : packageName)); + } + }, + (s, e) => + { + var logMessage = e.Data; + if (string.IsNullOrWhiteSpace(logMessage)) return; + this.Log().Error("[{0}] {1}".format_with(APP_NAME, logMessage)); + + if (ErrorRegex.IsMatch(logMessage) || ErrorNotFoundRegex.IsMatch(logMessage)) + { + var results = packageResults.GetOrAdd(packageToInstall, new PackageResult(packageToInstall, null, null)); + results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + }, + updateProcessPath: false + ); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + } + } + + return packageResults; + } + + /// + /// Grabs a value from the output based on the regex. + /// + /// The output. + /// The regex. + /// Name of the group. + /// + private static string get_value_from_output(string output, Regex regex, string groupName) + { + var matchGroup = regex.Match(output).Groups[groupName]; + if (matchGroup != null) + { + return matchGroup.Value; + } + + return string.Empty; + } + } +} \ No newline at end of file