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. ///