diff --git a/.uppercut b/.uppercut index ebb6b10233..5e354fe124 100644 --- a/.uppercut +++ b/.uppercut @@ -71,7 +71,7 @@ - + diff --git a/src/.nuget/packages.config b/src/.nuget/packages.config new file mode 100644 index 0000000000..7e62aa75e7 --- /dev/null +++ b/src/.nuget/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/chocolatey.sln b/src/chocolatey.sln index 039b034f42..b4b32a2e24 100644 --- a/src/chocolatey.sln +++ b/src/chocolatey.sln @@ -13,6 +13,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chocolatey.resources", "cho EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "chocolatey.tests.integration", "chocolatey.tests.integration\chocolatey.tests.integration.csproj", "{12ECAD4F-D463-4D72-A792-A4FCCFE9C456}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{7A36F8E0-B170-4A51-A788-F1246513808A}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index 2cce3bcb9d..cf65a3da3f 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -35,8 +35,9 @@ ..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll - - ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll + + False + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll ..\packages\Should.1.1.12.0\lib\Should.dll diff --git a/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs b/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs index ea00635e9e..80b2868545 100644 --- a/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/commands/CommandExecutorSpecs.cs @@ -25,8 +25,13 @@ public class CommandExecutorSpecs { public abstract class CommandExecutorSpecsBase : TinySpec { + protected readonly IFileSystem fileSystem = new DotNetFileSystem(); + protected CommandExecutor commandExecutor; + public override void Context() { + commandExecutor = new CommandExecutor(fileSystem); + } } @@ -34,7 +39,6 @@ public class when_CommandExecutor_errors : CommandExecutorSpecsBase { private int result; private string errorOutput; - private readonly IFileSystem file_system = new DotNetFileSystem(); public override void Context() { @@ -43,7 +47,7 @@ public override void Context() public override void Because() { - result = CommandExecutor.execute("cmd.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, file_system.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] @@ -74,7 +78,7 @@ public override void Because() { try { - CommandExecutor.execute("noprocess.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, null, (s, e) => { errorOutput += e.Data; }); + commandExecutor.execute("noprocess.exe", "/c bob123123", ApplicationParameters.DefaultWaitForExitInSeconds, null, (s, e) => { errorOutput += e.Data; }); } catch (Exception e) { diff --git a/src/chocolatey.tests.integration/packages.config b/src/chocolatey.tests.integration/packages.config index 35c5329590..6316acc715 100644 --- a/src/chocolatey.tests.integration/packages.config +++ b/src/chocolatey.tests.integration/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/chocolatey.tests/MockLogger.cs b/src/chocolatey.tests/MockLogger.cs index 8097fbdc24..ae054b0622 100644 --- a/src/chocolatey.tests/MockLogger.cs +++ b/src/chocolatey.tests/MockLogger.cs @@ -32,6 +32,10 @@ public enum LogLevel public class MockLogger : Mock, ILog, ILog { + public MockLogger() + { + } + private readonly Lazy>> _messages = new Lazy>>(); public ConcurrentDictionary> Messages @@ -57,61 +61,61 @@ public void LogMessage(LogLevel logLevel, string message) public void Debug(string message, params object[] formatting) { LogMessage(LogLevel.Debug, message.format_with(formatting)); - Object.Debug(message, formatting); + Object.Debug(message.format_with(formatting)); } public void Debug(Func message) { LogMessage(LogLevel.Debug, message()); - Object.Debug(message); + Object.Debug(message()); } public void Info(string message, params object[] formatting) { LogMessage(LogLevel.Info, message.format_with(formatting)); - Object.Info(message, formatting); + Object.Info(message.format_with(formatting)); } public void Info(Func message) { LogMessage(LogLevel.Info, message()); - Object.Info(message); + Object.Info(message()); } public void Warn(string message, params object[] formatting) { LogMessage(LogLevel.Warn, message.format_with(formatting)); - Object.Warn(message, formatting); + Object.Warn(message.format_with(formatting)); } public void Warn(Func message) { LogMessage(LogLevel.Warn, message()); - Object.Warn(message); + Object.Warn(message()); } public void Error(string message, params object[] formatting) { LogMessage(LogLevel.Error, message.format_with(formatting)); - Object.Error(message, formatting); + Object.Error(message.format_with(formatting)); } public void Error(Func message) { LogMessage(LogLevel.Error, message()); - Object.Error(message); + Object.Error(message()); } public void Fatal(string message, params object[] formatting) { LogMessage(LogLevel.Fatal, message.format_with(formatting)); - Object.Fatal(message, formatting); + Object.Fatal(message.format_with(formatting)); } public void Fatal(Func message) { LogMessage(LogLevel.Fatal, message()); - Object.Fatal(message); + Object.Fatal(message()); } } } \ No newline at end of file diff --git a/src/chocolatey.tests/TinySpec.cs b/src/chocolatey.tests/TinySpec.cs index 3873ce55f3..840d676ebf 100644 --- a/src/chocolatey.tests/TinySpec.cs +++ b/src/chocolatey.tests/TinySpec.cs @@ -27,13 +27,18 @@ namespace chocolatey.tests [TestFixture] public abstract class TinySpec { - public MockLogger MockLogger { get; set; } + private MockLogger _mockLogger; + + public MockLogger MockLogger + { + get { return _mockLogger; } + } [TestFixtureSetUp] public void Setup() { - MockLogger = new MockLogger(); - Log.InitializeWith(MockLogger); + _mockLogger = new MockLogger(); + Log.InitializeWith(_mockLogger); Context(); Because(); } @@ -65,8 +70,10 @@ public virtual void AfterEachSpec() [TestFixtureTearDown] public void TearDown() { + AfterObservations(); - MockLogger = new MockLogger(); + _mockLogger = null; + Log.InitializeWith(new NullLog()); } public virtual void AfterObservations() diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 50dae1f024..dd9ad62dea 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -32,11 +32,18 @@ 4 + + ..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll + ..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll - - ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll + + ..\packages\NuGet.Core.2.8.2\lib\net40-Client\NuGet.Core.dll + + + False + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll ..\packages\Should.1.1.12.0\lib\Should.dll @@ -62,6 +69,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs new file mode 100644 index 0000000000..85a0f384b6 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs @@ -0,0 +1,369 @@ +// 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 System.Collections.Concurrent; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Moq; + using NuGet; + using Should; + using chocolatey.infrastructure.adapters; + using chocolatey.infrastructure.app; + using chocolatey.infrastructure.app.configuration; + using chocolatey.infrastructure.app.domain; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.commands; + using chocolatey.infrastructure.results; + using IFileSystem = chocolatey.infrastructure.filesystem.IFileSystem; + + //todo: come back and fix the logging tests - determine why it gets cycled out during test suite runs. + + public class AutomaticUninstallerServiceSpecs + { + public abstract class AutomaticUninstallerServiceSpecsBase : TinySpec + { + protected AutomaticUninstallerService service; + protected Mock packageInfoService = new Mock(); + protected Mock fileSystem = new Mock(); + protected Mock process = new Mock(); + protected Mock registryService = new Mock(); + protected Mock commandExecutor = new Mock(); + protected ChocolateyConfiguration config = new ChocolateyConfiguration(); + protected Mock package = new Mock(); + protected ConcurrentDictionary packageResults = new ConcurrentDictionary(); + protected PackageResult packageResult; + protected ChocolateyPackageInformation packageInformation; + protected IList registryKeys = new List(); + protected IInstaller installerType = new CustomInstaller(); + + protected readonly string originalUninstallString = @"""C:\Program Files (x86)\WinDirStat\Uninstall.exe"""; + protected readonly string expectedUninstallString = @"C:\Program Files (x86)\WinDirStat\Uninstall.exe"; + + public override void Context() + { + CommandExecutor.initialize_with(new Lazy(() => fileSystem.Object), () => process.Object); + + service = new AutomaticUninstallerService(packageInfoService.Object, fileSystem.Object, registryService.Object, commandExecutor.Object); + config.Features.AutoUninstaller = true; + package.Setup(p => p.Id).Returns("regular"); + package.Setup(p => p.Version).Returns(new SemanticVersion("1.2.0")); + packageResult = new PackageResult(package.Object, null); + packageInformation = new ChocolateyPackageInformation(package.Object); + registryKeys.Add(new RegistryApplicationKey + { + InstallLocation = @"C:\Program Files (x86)\WinDirStat", + UninstallString = originalUninstallString, + HasQuietUninstall = false, + KeyPath = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinDirStat", + }); + packageInformation.RegistrySnapshot = new Registry("123", registryKeys); + packageInfoService.Setup(s => s.get_package_information(package.Object)).Returns(packageInformation); + 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); + fileSystem.Setup(f => f.get_full_path(expectedUninstallString)).Returns(expectedUninstallString); + } + } + + public class when_autouninstall_feature_is_off : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.Context(); + config.Features.AutoUninstaller = false; + } + + public override void Because() + { + service.run(packageResult, config); + } + + //[Fact] + //public void should_log_why_it_skips_auto_uninstaller() + //{ + // // will pass by itself, but won't pass as part of the test suite. + // MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - AutoUninstaller feature is not enabled."), Times.Once); + //} + + [Fact] + public void should_not_get_package_information() + { + packageInfoService.Verify(s => s.get_package_information(It.IsAny()), Times.Never); + } + + [Fact] + public void should_not_call_command_executor() + { + commandExecutor.Verify(c => c.execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_registry_snapshot_is_null : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.Context(); + packageInformation.RegistrySnapshot = null; + } + + public override void Because() + { + service.run(packageResult, config); + } + + //[Fact] + //public void should_log_why_it_skips_auto_uninstaller() + //{ + // MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - No registry snapshot."), Times.Once); + //} + + [Fact] + public void should_not_call_command_executor() + { + commandExecutor.Verify(c => c.execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_registry_keys_are_empty : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.Context(); + packageInformation.RegistrySnapshot = new Registry("123", null); + } + + public override void Because() + { + service.run(packageResult, config); + } + + //[Fact] + //public void should_log_why_it_skips_auto_uninstaller() + //{ + // MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - No registry keys in snapshot."), Times.Once); + //} + + [Fact] + public void should_not_call_command_executor() + { + commandExecutor.Verify(c => c.execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_install_location_does_not_exist : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.Context(); + fileSystem.ResetCalls(); + fileSystem.Setup(f => f.directory_exists(registryKeys.FirstOrDefault().InstallLocation)).Returns(false); + } + + public override void Because() + { + service.run(packageResult, config); + } + + //[Fact] + //public void should_log_why_it_skips_auto_uninstaller() + //{ + // MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - The application appears to have been uninstalled already by other means."), Times.Once); + //} + + [Fact] + public void should_not_call_command_executor() + { + commandExecutor.Verify(c => c.execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_registry_location_does_not_exist : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.Context(); + registryService.ResetCalls(); + registryService.Setup(r => r.value_exists(registryKeys.FirstOrDefault().KeyPath, ApplicationParameters.RegistryValueInstallLocation)).Returns(false); + } + + public override void Because() + { + service.run(packageResult, config); + } + + //[Fact] + //public void should_log_why_it_skips_auto_uninstaller() + //{ + // MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - The application appears to have been uninstalled already by other means."), Times.Once); + //} + + [Fact] + public void should_not_call_command_executor() + { + commandExecutor.Verify(c => c.execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_registry_location_and_install_location_both_do_not_exist : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.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); + } + + public override void Because() + { + service.run(packageResult, config); + } + + //[Fact] + //public void should_log_why_it_skips_auto_uninstaller() + //{ + // MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - The application appears to have been uninstalled already by other means."), Times.Once); + //} + + [Fact] + public void should_not_call_command_executor() + { + commandExecutor.Verify(c => c.execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_AutomaticUninstallerService_is_run_normally : AutomaticUninstallerServiceSpecsBase + { + public override void Because() + { + service.run(packageResult, config); + } + + [Fact] + public void should_call_get_package_information() + { + packageInfoService.Verify(s => s.get_package_information(It.IsAny()), Times.Once); + } + + [Fact] + public void should_call_command_executor() + { + commandExecutor.Verify(c => c.execute(expectedUninstallString, installerType.build_uninstall_command_arguments().trim_safe(), It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + } + + public class when_AutomaticUninstallerService_defines_uninstall_switches : AutomaticUninstallerServiceSpecsBase + { + private Action because; + private readonly string registryUninstallArgs = "bob"; + + public override void Because() + { + because = () => service.run(packageResult, config); + } + + public void reset() + { + Context(); + } + + private void test_installertype(IInstaller installer,bool useInstallerDefaultArgs) + { + reset(); + registryKeys.Add(new RegistryApplicationKey + { + InstallLocation = @"C:\Program Files (x86)\WinDirStat", + UninstallString = "{0} {1}".format_with(originalUninstallString,registryUninstallArgs), + HasQuietUninstall = false, + KeyPath = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinDirStat", + InstallerType = installer.InstallerType, + }); + packageInformation.RegistrySnapshot = new Registry("123", registryKeys); + + because(); + + var uninstallArgs = useInstallerDefaultArgs ? installer.build_uninstall_command_arguments().trim_safe() : registryUninstallArgs.trim_safe(); + + commandExecutor.Verify(c => c.execute(expectedUninstallString, uninstallArgs, It.IsAny(), It.IsAny>(), It.IsAny>()), Times.Never); + } + + [Fact] + public void should_use_CustomInstaller_uninstall_args_when_installtype_is_unknown_and_has_quiet_uninstall_is_false() + { + test_installertype(new CustomInstaller(), useInstallerDefaultArgs: true); + } + + [Fact] + public void should_use_registry_uninstall_args_when_installtype_is_unknown_and_has_quiet_uninstall_is_true() + { + test_installertype(new CustomInstaller(), useInstallerDefaultArgs: false); + } + + [Fact] + public void should_use_MsiInstaller_uninstall_args_when_installtype_is_msi_and_has_quiet_uninstall_is_false() + { + test_installertype(new MsiInstaller(), useInstallerDefaultArgs: true); + } + + [Fact] + public void should_use_registry_uninstall_args_when_installtype_is_msi_and_has_quiet_uninstall_is_true() + { + test_installertype(new MsiInstaller(), useInstallerDefaultArgs: false); + } + + [Fact] + public void should_use_InnoSetupInstaller_uninstall_args_when_installtype_is_innosetup_and_has_quiet_uninstall_is_false() + { + test_installertype(new InnoSetupInstaller(), useInstallerDefaultArgs: true); + } + + [Fact] + public void should_use_registry_uninstall_args_when_installtype_is_innosetup_and_has_quiet_uninstall_is_true() + { + test_installertype(new InnoSetupInstaller(), useInstallerDefaultArgs: false); + } + + [Fact] + public void should_use_InstallShieldInstaller_uninstall_args_when_installtype_is_installshield_and_has_quiet_uninstall_is_false() + { + test_installertype(new InstallShieldInstaller(), useInstallerDefaultArgs: true); + } + + [Fact] + public void should_use_registry_uninstall_args_when_installtype_is_installshield_and_has_quiet_uninstall_is_true() + { + test_installertype(new InstallShieldInstaller(), useInstallerDefaultArgs: false); + } + + [Fact] + public void should_use_NsisInstaller_uninstall_args_when_installtype_is_nsis_and_has_quiet_uninstall_is_false() + { + test_installertype(new NsisInstaller(), useInstallerDefaultArgs: true); + } + + [Fact] + public void should_use_registry_uninstall_args_when_installtype_is_nsis_and_has_quiet_uninstall_is_true() + { + test_installertype(new NsisInstaller(), useInstallerDefaultArgs: false); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey.tests/infrastructure/commands/CommandExecutorSpecs.cs b/src/chocolatey.tests/infrastructure/commands/CommandExecutorSpecs.cs index c2c3a6e9d7..d13756d9c4 100644 --- a/src/chocolatey.tests/infrastructure/commands/CommandExecutorSpecs.cs +++ b/src/chocolatey.tests/infrastructure/commands/CommandExecutorSpecs.cs @@ -27,12 +27,14 @@ public class CommandExecutorSpecs { public abstract class CommandExecutorSpecsBase : TinySpec { - protected Mock file_system = new Mock(); + protected Mock fileSystem = new Mock(); protected Mock process = new Mock(); + protected CommandExecutor commandExecutor; public override void Context() { - CommandExecutor.initialize_with(new Lazy(() => file_system.Object), () => process.Object); + commandExecutor = new CommandExecutor(fileSystem.Object); + CommandExecutor.initialize_with(new Lazy(() => fileSystem.Object), () => process.Object); } } @@ -49,7 +51,7 @@ public override void Context() public override void Because() { - result = CommandExecutor.execute("bob", "args", ApplicationParameters.DefaultWaitForExitInSeconds); + result = commandExecutor.execute("bob", "args", ApplicationParameters.DefaultWaitForExitInSeconds); } [Fact] @@ -108,7 +110,7 @@ public override void Context() public override void Because() { - result = CommandExecutor.execute("bob", "args", ApplicationParameters.DefaultWaitForExitInSeconds); + result = commandExecutor.execute("bob", "args", ApplicationParameters.DefaultWaitForExitInSeconds); } [Fact] @@ -136,7 +138,7 @@ public class when_CommandExecutor_does_not_wait_for_exit : CommandExecutorSpecsB public override void Because() { - result = CommandExecutor.execute("bob", "args", waitForExitInSeconds: 0); + result = commandExecutor.execute("bob", "args", waitForExitInSeconds: 0); } [Fact] diff --git a/src/chocolatey.tests/packages.config b/src/chocolatey.tests/packages.config index c635152d4e..585fd5cb33 100644 --- a/src/chocolatey.tests/packages.config +++ b/src/chocolatey.tests/packages.config @@ -1,8 +1,10 @@  + - + + \ No newline at end of file diff --git a/src/chocolatey/StringExtensions.cs b/src/chocolatey/StringExtensions.cs index 867177de4f..767e338258 100644 --- a/src/chocolatey/StringExtensions.cs +++ b/src/chocolatey/StringExtensions.cs @@ -31,6 +31,8 @@ public static class StringExtensions /// A formatted string. public static string format_with(this string input, params object[] formatting) { + if (string.IsNullOrWhiteSpace(input)) return string.Empty; + return string.Format(input, formatting); } diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index cab8bd8de9..8554e78060 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -86,10 +86,12 @@ + + @@ -124,6 +126,7 @@ + @@ -145,6 +148,7 @@ + @@ -214,7 +218,9 @@ - + + Designer + diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 45e6a56f06..984266a7b8 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -47,6 +47,7 @@ public static class ApplicationParameters public static string ChocolateyPackageInfoStoreLocation = _fileSystem.combine_paths(InstallLocation, ".chocolatey"); public static readonly string ChocolateyCommunityFeedPushSource = "https://chocolatey.org/"; public static readonly string UserAgent = "Chocolatey Command Line"; + public static readonly string RegistryValueInstallLocation = "InstallLocation"; /// /// Default is 45 minutes @@ -59,6 +60,13 @@ public static class Tools public static readonly string ShimGenExe = _fileSystem.combine_paths(InstallLocation, "tools", "shimgen.exe"); } + public static class Features + { + public static readonly string CheckSumFiles = "checksumFiles"; + public static readonly string VirusCheckFiles = "virusCheckFiles"; + public static readonly string AutoUninstaller = "autoUninstaller"; + } + public static class Messages { public static readonly string ContinueChocolateyAction = "Moving forward with chocolatey actions."; diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index 50391bf890..c54e234dbc 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -79,8 +79,6 @@ private static void set_file_configuration(ChocolateyConfiguration config, IFile config.Sources = sources.Remove(sources.Length - 1, 1).ToString(); } - config.CheckSumFiles = configFileSettings.ChecksumFiles; - config.VirusCheckFiles = configFileSettings.VirusCheckFiles; config.CacheLocation = configFileSettings.CacheLocation; config.ContainsLegacyPackageInstalls = configFileSettings.ContainsLegacyPackageInstalls; if (configFileSettings.CommandExecutionTimeoutSeconds <= 0) @@ -89,8 +87,11 @@ private static void set_file_configuration(ChocolateyConfiguration config, IFile } config.CommandExecutionTimeoutSeconds = configFileSettings.CommandExecutionTimeoutSeconds; + set_feature_flags(config, configFileSettings); + try { + // save so all updated configuration items get set to existing config xmlService.serialize(configFileSettings, globalConfigPath); } catch (Exception ex) @@ -99,6 +100,21 @@ private static void set_file_configuration(ChocolateyConfiguration config, IFile } } + private static void set_feature_flags(ChocolateyConfiguration config, ConfigFileSettings configFileSettings) + { + config.Features.CheckSumFiles = set_feature_flag(ApplicationParameters.Features.CheckSumFiles, config, configFileSettings); + config.Features.VirusCheckFiles = set_feature_flag(ApplicationParameters.Features.VirusCheckFiles, config, configFileSettings); + config.Features.AutoUninstaller = set_feature_flag(ApplicationParameters.Features.AutoUninstaller, config, configFileSettings); + } + + private static bool set_feature_flag(string featureName, ChocolateyConfiguration config, ConfigFileSettings configFileSettings) + { + var feature = configFileSettings.Features.FirstOrDefault(f => f.Name.is_equal_to(featureName)); + if (feature != null && feature.Enabled) return true; + + return false; + } + private static void set_global_options(IList args, ChocolateyConfiguration config) { ConfigurationOptions.parse_arguments_and_update_configuration( diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 1c007b9c4e..512fef5b28 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -32,6 +32,7 @@ public ChocolateyConfiguration() RegularOuptut = true; PromptForConfirmation = true; Information = new InformationCommandConfiguration(); + Features = new FeaturesConfiguration(); NewCommand = new NewCommandConfiguration(); ListCommand = new ListCommandConfiguration(); SourceCommand = new SourcesCommandConfiguration(); @@ -93,9 +94,6 @@ private void output_tostring(StringBuilder propertyValues, IEnumerable public InformationCommandConfiguration Information { get; private set; } + /// + /// Configuration related to features and whether they are enabled. + /// + public FeaturesConfiguration Features { get; private set; } + /// /// Configuration related specifically to List command /// @@ -175,6 +178,7 @@ private void output_tostring(StringBuilder propertyValues, IEnumerable public PushCommandConfiguration PushCommand { get; private set; } + } public sealed class InformationCommandConfiguration @@ -187,6 +191,13 @@ public sealed class InformationCommandConfiguration public bool IsInteractive { get; set; } } + public sealed class FeaturesConfiguration + { + public bool AutoUninstaller { get; set; } + public bool CheckSumFiles { get; set; } + public bool VirusCheckFiles { get; set; } + } + //todo: retrofit other command configs this way public sealed class ListCommandConfiguration diff --git a/src/chocolatey/infrastructure.app/configuration/ConfigFileFeatureSetting.cs b/src/chocolatey/infrastructure.app/configuration/ConfigFileFeatureSetting.cs new file mode 100644 index 0000000000..f1ad665537 --- /dev/null +++ b/src/chocolatey/infrastructure.app/configuration/ConfigFileFeatureSetting.cs @@ -0,0 +1,34 @@ +// 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.configuration +{ + using System; + using System.Xml.Serialization; + + /// + /// XML config file features element + /// + [Serializable] + [XmlType("feature")] + public sealed class ConfigFileFeatureSetting + { + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + + [XmlAttribute(AttributeName = "enabled")] + public bool Enabled { get; set; } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/configuration/ConfigFileSettings.cs b/src/chocolatey/infrastructure.app/configuration/ConfigFileSettings.cs index 0893f45bc1..355804ac06 100644 --- a/src/chocolatey/infrastructure.app/configuration/ConfigFileSettings.cs +++ b/src/chocolatey/infrastructure.app/configuration/ConfigFileSettings.cs @@ -26,12 +26,6 @@ namespace chocolatey.infrastructure.app.configuration [XmlRoot("chocolatey")] public class ConfigFileSettings { - [XmlElement(ElementName = "checksumFiles")] - public bool ChecksumFiles { get; set; } - - [XmlElement(ElementName = "virusCheckFiles")] - public bool VirusCheckFiles { get; set; } - [XmlElement(ElementName = "cacheLocation")] public string CacheLocation { get; set; } @@ -42,7 +36,10 @@ public class ConfigFileSettings public int CommandExecutionTimeoutSeconds { get; set; } [XmlArray("sources")] - public HashSet Sources { get; set; } + public HashSet Sources { get; set; } + + [XmlArray("features")] + public HashSet Features { get; set; } [XmlArray("apiKeys")] public HashSet ApiKeys { get; set; } diff --git a/src/chocolatey/infrastructure.app/configuration/chocolatey.config b/src/chocolatey/infrastructure.app/configuration/chocolatey.config index aed3f1e645..3da507bf8d 100644 --- a/src/chocolatey/infrastructure.app/configuration/chocolatey.config +++ b/src/chocolatey/infrastructure.app/configuration/chocolatey.config @@ -1,11 +1,13 @@  - false - true - false true + + + + + \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/domain/CustomInstaller.cs b/src/chocolatey/infrastructure.app/domain/CustomInstaller.cs index 3f99f3d394..e4b98242dc 100644 --- a/src/chocolatey/infrastructure.app/domain/CustomInstaller.cs +++ b/src/chocolatey/infrastructure.app/domain/CustomInstaller.cs @@ -35,6 +35,11 @@ public CustomInstaller() ValidExitCodes = new List {0}; } + public InstallerType InstallerType + { + get { return InstallerType.Custom; } + } + public string InstallExecutable { get; private set; } public string SilentInstall { get; private set; } public string NoReboot { get; private set; } diff --git a/src/chocolatey/infrastructure.app/domain/IInstaller.cs b/src/chocolatey/infrastructure.app/domain/IInstaller.cs index f5d44c3c69..273a88a852 100644 --- a/src/chocolatey/infrastructure.app/domain/IInstaller.cs +++ b/src/chocolatey/infrastructure.app/domain/IInstaller.cs @@ -19,6 +19,7 @@ namespace chocolatey.infrastructure.app.domain public interface IInstaller { + InstallerType InstallerType { get; } string InstallExecutable { get; } string SilentInstall { get; } string NoReboot { get; } diff --git a/src/chocolatey/infrastructure.app/domain/InnoSetupInstaller.cs b/src/chocolatey/infrastructure.app/domain/InnoSetupInstaller.cs index 16f1567434..1a5ec4d1e8 100644 --- a/src/chocolatey/infrastructure.app/domain/InnoSetupInstaller.cs +++ b/src/chocolatey/infrastructure.app/domain/InnoSetupInstaller.cs @@ -41,6 +41,11 @@ public InnoSetupInstaller() ValidExitCodes = new List {0}; } + public InstallerType InstallerType + { + get { return InstallerType.InnoSetup; } + } + public string InstallExecutable { get; private set; } public string SilentInstall { get; private set; } public string NoReboot { get; private set; } diff --git a/src/chocolatey/infrastructure.app/domain/InstallShieldInstaller.cs b/src/chocolatey/infrastructure.app/domain/InstallShieldInstaller.cs index 96e14087cb..8b009073b3 100644 --- a/src/chocolatey/infrastructure.app/domain/InstallShieldInstaller.cs +++ b/src/chocolatey/infrastructure.app/domain/InstallShieldInstaller.cs @@ -41,6 +41,11 @@ public InstallShieldInstaller() ValidExitCodes = new List {0}; } + public InstallerType InstallerType + { + get { return InstallerType.InstallShield; } + } + public string InstallExecutable { get; private set; } public string SilentInstall { get; private set; } public string NoReboot { get; private set; } diff --git a/src/chocolatey/infrastructure.app/domain/MsiInstaller.cs b/src/chocolatey/infrastructure.app/domain/MsiInstaller.cs index 732261ca4a..b082cde4e9 100644 --- a/src/chocolatey/infrastructure.app/domain/MsiInstaller.cs +++ b/src/chocolatey/infrastructure.app/domain/MsiInstaller.cs @@ -40,7 +40,7 @@ public MsiInstaller() // http://msdn.microsoft.com/en-us/library/aa372064.aspx CustomInstallLocation = "TARGETDIR=\"{0}\"".format_with(InstallTokens.CUSTOM_INSTALL_LOCATION); // http://msdn.microsoft.com/en-us/library/aa370856.aspx - Language = "ProductLanguage={LANGUAGE}".format_with(InstallTokens.LANGUAGE); + Language = "ProductLanguage={0}".format_with(InstallTokens.LANGUAGE); // http://msdn.microsoft.com/en-us/library/aa367559.aspx OtherInstallOptions = "ALLUSERS=1 DISABLEDESKTOPSHORTCUT=1 ADDDESKTOPICON=0 ADDSTARTMENU=0"; UninstallExecutable = "msiexec.exe"; @@ -50,6 +50,11 @@ public MsiInstaller() ValidExitCodes = new List {0, 3010}; } + public InstallerType InstallerType + { + get { return InstallerType.Msi; } + } + public string InstallExecutable { get; private set; } public string SilentInstall { get; private set; } public string NoReboot { get; private set; } diff --git a/src/chocolatey/infrastructure.app/domain/NsisInstaller.cs b/src/chocolatey/infrastructure.app/domain/NsisInstaller.cs index fc2748a0a4..a0b4f7c05f 100644 --- a/src/chocolatey/infrastructure.app/domain/NsisInstaller.cs +++ b/src/chocolatey/infrastructure.app/domain/NsisInstaller.cs @@ -43,6 +43,11 @@ public NsisInstaller() ValidExitCodes = new List {0}; } + public InstallerType InstallerType + { + get { return InstallerType.Nsis; } + } + public string InstallExecutable { get; private set; } public string SilentInstall { get; private set; } public string NoReboot { get; private set; } diff --git a/src/chocolatey/infrastructure.app/domain/Registry.cs b/src/chocolatey/infrastructure.app/domain/Registry.cs index 832f9bbf67..a7055654f5 100644 --- a/src/chocolatey/infrastructure.app/domain/Registry.cs +++ b/src/chocolatey/infrastructure.app/domain/Registry.cs @@ -43,7 +43,14 @@ public Registry() public Registry(string user, IEnumerable keys) { User = user; - RegistryKeys = keys.ToList(); + if (keys != null) + { + RegistryKeys = keys.ToList(); + } + else + { + RegistryKeys = new List(); + } } [XmlElement(ElementName = "user")] diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index c6d01335f7..f2814c26d9 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -57,6 +57,8 @@ public void RegisterComponents(Container container) container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); + container.Register(Lifestyle.Singleton); + container.Register(Lifestyle.Singleton); //todo:refactor - this should be autowired container.Register>(() => diff --git a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs new file mode 100644 index 0000000000..1cad92f1af --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs @@ -0,0 +1,146 @@ +// 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.Generic; + using System.Linq; + using configuration; + using domain; + using filesystem; + using infrastructure.commands; + using results; + + public class AutomaticUninstallerService : IAutomaticUninstallerService + { + private readonly IChocolateyPackageInformationService _packageInfoService; + private readonly IFileSystem _fileSystem; + private readonly IRegistryService _registryService; + private readonly ICommandExecutor _commandExecutor; + + public AutomaticUninstallerService(IChocolateyPackageInformationService packageInfoService, IFileSystem fileSystem, IRegistryService registryService, ICommandExecutor commandExecutor) + { + _packageInfoService = packageInfoService; + _fileSystem = fileSystem; + _registryService = registryService; + _commandExecutor = commandExecutor; + } + + public void run(PackageResult packageResult, ChocolateyConfiguration config) + { + if (!config.Features.AutoUninstaller) + { + this.Log().Info(" Skipping auto uninstaller - AutoUninstaller feature is not enabled."); + return; + } + + var pkgInfo = _packageInfoService.get_package_information(packageResult.Package); + + if (pkgInfo.RegistrySnapshot == null) + { + this.Log().Info(" Skipping auto uninstaller - No registry snapshot."); + return; + } + + var registryKeys = pkgInfo.RegistrySnapshot.RegistryKeys; + if (registryKeys == null || registryKeys.Count == 0) + { + this.Log().Info(" Skipping auto uninstaller - No registry keys in snapshot."); + return; + } + + this.Log().Info(" Running auto uninstaller..."); + + foreach (var key in registryKeys.or_empty_list_if_null()) + { + this.Log().Debug(() => " Preparing uninstall key '{0}'".format_with(key.UninstallString)); + + if (!_fileSystem.directory_exists(key.InstallLocation) || !_registryService.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))); + continue; + } + + // split on " /" and " -" for quite a bit more accuracy + IList uninstallArgs = key.UninstallString.to_string().Split(new[] {" /", " -"}, StringSplitOptions.RemoveEmptyEntries).ToList(); + var uninstallExe = uninstallArgs.DefaultIfEmpty(string.Empty).FirstOrDefault(); + uninstallArgs.Remove(uninstallExe); + uninstallExe = uninstallExe.remove_surrounding_quotes(); + this.Log().Debug(() => " Uninstaller path is '{0}'".format_with(uninstallExe)); + + //todo: ultimately we should merge keys with logging + if (!key.HasQuietUninstall) + { + IInstaller installer = new CustomInstaller(); + + switch (key.InstallerType) + { + case InstallerType.Msi: + installer = new MsiInstaller(); + break; + case InstallerType.InnoSetup: + installer = new InnoSetupInstaller(); + break; + case InstallerType.Nsis: + installer = new NsisInstaller(); + break; + case InstallerType.InstallShield: + installer = new CustomInstaller(); + break; + default: + // skip + break; + } + + this.Log().Debug(() => " Installer type is '{0}'".format_with(installer.GetType().Name)); + + uninstallArgs.Add(installer.build_uninstall_command_arguments()); + } + + this.Log().Debug(() => " Args are '{0}'".format_with(uninstallArgs.@join(" "))); + + var exitCode = _commandExecutor.execute( + uninstallExe, + uninstallArgs.@join(" "), + config.CommandExecutionTimeoutSeconds, + (s, e) => + { + if (e == null || string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Debug(() => " [AutoUninstaller] {0}".format_with(e.Data)); + }, + (s, e) => + { + if (e == null || string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => " [AutoUninstaller] {0}".format_with(e.Data)); + }); + + if (exitCode != 0) + { + Environment.ExitCode = exitCode; + string logMessage = " Auto uninstaller failed. Please remove machine installation manually."; + this.Log().Error(() => logMessage); + packageResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + } + else + { + this.Log().Info(() => " Auto uninstaller has successfully uninstalled {0} from your machine.".format_with(packageResult.Package.Id)); + } + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index f8fdb9bb6c..d4197e6514 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -24,7 +24,6 @@ namespace chocolatey.infrastructure.app.services using configuration; using domain; using filesystem; - using infrastructure.commands; using logging; using platforms; using results; @@ -37,8 +36,9 @@ public class ChocolateyPackageService : IChocolateyPackageService private readonly IFileSystem _fileSystem; private readonly IRegistryService _registryService; private readonly IChocolateyPackageInformationService _packageInfoService; + private readonly IAutomaticUninstallerService _autoUninstallerService; - public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService) + public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService, IAutomaticUninstallerService autoUninstallerService) { _nugetService = nugetService; _powershellService = powershellService; @@ -46,6 +46,7 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p _fileSystem = fileSystem; _registryService = registryService; _packageInfoService = packageInfoService; + _autoUninstallerService = autoUninstallerService; } public void list_noop(ChocolateyConfiguration config) @@ -304,90 +305,12 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi _shimgenService.uninstall(config, packageResult); - var powershellRan = false; if (!config.SkipPackageInstallProvider) { - powershellRan = _powershellService.uninstall(config, packageResult); + _powershellService.uninstall(config, packageResult); } - //todo run autouninstaller every time - to do this you must determine if the install path still exists. - if (!powershellRan) - { - var pkgInfo = _packageInfoService.get_package_information(packageResult.Package); - - if (pkgInfo.RegistrySnapshot != null) - { - //todo:refactor this crap - this.Log().Info(" Running AutoUninstaller..."); - - foreach (var key in pkgInfo.RegistrySnapshot.RegistryKeys.or_empty_list_if_null()) - { - this.Log().Debug(() => " Preparing uninstall key '{0}'".format_with(key.UninstallString)); - // split on " /" and " -" for quite a bit more accuracy - IList uninstallArgs = key.UninstallString.to_string().Split(new[] {" /", " -"}, StringSplitOptions.RemoveEmptyEntries).ToList(); - var uninstallExe = uninstallArgs.DefaultIfEmpty(string.Empty).FirstOrDefault().remove_surrounding_quotes(); - this.Log().Debug(() => " Uninstaller path is '{0}'".format_with(uninstallExe)); - uninstallArgs.Remove(uninstallExe); - - if (!key.HasQuietUninstall) - { - IInstaller installer = new CustomInstaller(); - - //refactor this to elsewhere - switch (key.InstallerType) - { - case InstallerType.Msi: - installer = new MsiInstaller(); - break; - case InstallerType.InnoSetup: - installer = new InnoSetupInstaller(); - break; - case InstallerType.Nsis: - installer = new NsisInstaller(); - break; - case InstallerType.InstallShield: - installer = new CustomInstaller(); - break; - default: - // skip - - break; - } - - this.Log().Debug(() => " Installer type is '{0}'".format_with(installer.GetType().Name)); - - uninstallArgs.Add(installer.build_uninstall_command_arguments()); - } - - this.Log().Debug(() => " Args are '{0}'".format_with(uninstallArgs.join(" "))); - - var exitCode = CommandExecutor.execute( - uninstallExe, uninstallArgs.join(" "), config.CommandExecutionTimeoutSeconds, - (s, e) => - { - if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Debug(() => " [AutoUninstaller] {0}".format_with(e.Data)); - }, - (s, e) => - { - if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Error(() => " [AutoUninstaller] {0}".format_with(e.Data)); - }); - - if (exitCode != 0) - { - Environment.ExitCode = exitCode; - string logMessage = " Auto uninstaller failed. Please remove machine installation manually."; - this.Log().Error(() => logMessage); - packageResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); - } - else - { - this.Log().Info(() => " AutoUninstaller has successfully uninstalled {0} from your machine install".format_with(packageResult.Package.Id)); - } - } - } - } + _autoUninstallerService.run(packageResult, config); if (packageResult.Success) { diff --git a/src/chocolatey/infrastructure.app/services/IAutomaticUninstallerService.cs b/src/chocolatey/infrastructure.app/services/IAutomaticUninstallerService.cs new file mode 100644 index 0000000000..31ae030948 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/IAutomaticUninstallerService.cs @@ -0,0 +1,33 @@ +// 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 configuration; + using results; + + /// + /// The automagic uninstaller service + /// + public interface IAutomaticUninstallerService + { + /// + /// Runs to remove a application from the registry. + /// + /// The package result. + /// The configuration. + void run(PackageResult packageResult, ChocolateyConfiguration config); + } +} \ 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 eaebc49f66..af451bcc01 100644 --- a/src/chocolatey/infrastructure.app/services/IRegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/IRegistryService.cs @@ -23,5 +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); } } \ 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 97650f76fa..bbf4ebb1dd 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -202,6 +202,11 @@ public void save_to_file(Registry snapshot, string filePath) _xmlService.serialize(snapshot, filePath); } + public bool value_exists(string keyPath, string value) + { + return Microsoft.Win32.Registry.GetValue(keyPath, value, null) != null; + } + public Registry read_from_file(string filePath) { if (!_fileSystem.file_exists(filePath)) diff --git a/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs b/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs index 1428477e93..3581401400 100644 --- a/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs +++ b/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs @@ -26,6 +26,7 @@ namespace chocolatey.infrastructure.app.services public class ShimGenerationService : IShimGenerationService { private readonly IFileSystem _fileSystem; + private readonly ICommandExecutor _commandExecutor; private const string PATH_TOKEN = "{{path}}"; private const string ICON_PATH_TOKEN = "{{icon_path}}"; private const string OUTPUT_TOKEN = "{{output}}"; @@ -33,9 +34,10 @@ public class ShimGenerationService : IShimGenerationService private readonly IDictionary _shimGenArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - public ShimGenerationService(IFileSystem fileSystem) + public ShimGenerationService(IFileSystem fileSystem,ICommandExecutor commandExecutor) { _fileSystem = fileSystem; + _commandExecutor = commandExecutor; set_shimgen_args_dictionary(); } @@ -92,7 +94,7 @@ public void install(ChocolateyConfiguration configuration, PackageResult package argsForPackage += " --gui"; } - var exitCode = CommandExecutor.execute( + var exitCode = _commandExecutor.execute( _shimGenExePath, argsForPackage, configuration.CommandExecutionTimeoutSeconds, (s, e) => { diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index e7b04480ee..134336af5b 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -31,13 +31,15 @@ public interface IWebPiService //todo this is the old nuget.exe installer code that needs cleaned up for webpi public class WebPiService : IWebPiService { + private readonly ICommandExecutor _commandExecutor; 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); - public WebPiService() + public WebPiService(ICommandExecutor commandExecutor) { + _commandExecutor = commandExecutor; set_cmd_args_dictionaries(); } @@ -80,7 +82,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu foreach (var packageToInstall in configuration.PackageNames.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)) { var argsForPackage = args.Replace(PACKAGE_NAME_TOKEN, packageToInstall); - var exitCode = CommandExecutor.execute( + var exitCode = _commandExecutor.execute( _webPiExePath, argsForPackage, configuration.CommandExecutionTimeoutSeconds, (s, e) => { diff --git a/src/chocolatey/infrastructure/commands/CommandExecutor.cs b/src/chocolatey/infrastructure/commands/CommandExecutor.cs index f557d6c492..6533976eb7 100644 --- a/src/chocolatey/infrastructure/commands/CommandExecutor.cs +++ b/src/chocolatey/infrastructure/commands/CommandExecutor.cs @@ -24,8 +24,13 @@ namespace chocolatey.infrastructure.commands using platforms; using Process = adapters.Process; - public sealed class CommandExecutor + public sealed class CommandExecutor : ICommandExecutor { + public CommandExecutor(IFileSystem fileSystem) + { + file_system_initializer = new Lazy(() => fileSystem); + } + private static Lazy file_system_initializer = new Lazy(() => new DotNetFileSystem()); private static IFileSystem file_system @@ -42,12 +47,12 @@ public static void initialize_with(Lazy file_system, Func initialize_process = process_initializer; } - public static int execute(string process, string arguments, int waitForExitInSeconds) + public int execute(string process, string arguments, int waitForExitInSeconds) { return execute(process, arguments, waitForExitInSeconds, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); } - public static int execute( + public int execute( string process, string arguments, int waitForExitInSeconds, @@ -65,7 +70,7 @@ Action stdErrAction ); } - public static int execute(string process, string arguments, int waitForExitInSeconds, string workingDirectory) + public int execute(string process, string arguments, int waitForExitInSeconds, string workingDirectory) { return execute(process, arguments, waitForExitInSeconds, workingDirectory, null, null, updateProcessPath: true); } @@ -143,12 +148,12 @@ bool updateProcessPath private static void log_output(object sender, DataReceivedEventArgs e) { - "chocolatey".Log().Info(e.Data); + if (e != null) "chocolatey".Log().Info(e.Data); } private static void log_error(object sender, DataReceivedEventArgs e) { - "chocolatey".Log().Error(e.Data); + if (e != null) "chocolatey".Log().Error(e.Data); } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure/commands/ICommandExecutor.cs b/src/chocolatey/infrastructure/commands/ICommandExecutor.cs new file mode 100644 index 0000000000..585122d1cd --- /dev/null +++ b/src/chocolatey/infrastructure/commands/ICommandExecutor.cs @@ -0,0 +1,34 @@ +// 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.commands +{ + using System; + using System.Diagnostics; + + public interface ICommandExecutor + { + int execute(string process, string arguments, int waitForExitInSeconds); + int execute(string process, string arguments, int waitForExitInSeconds, string workingDirectory); + + int execute( + string process, + string arguments, + int waitForExitInSeconds, + Action stdOutAction, + Action stdErrAction + ); + } +} \ No newline at end of file