diff --git a/CHANGELOG.md b/CHANGELOG.md index d92595b7cd..9597d454b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,22 @@ This covers changes for the "chocolatey" and "chocolatey.lib" packages, which ar ## [0.10.6](https://github.com/chocolatey/choco/issues?q=milestone%3A0.10.6+is%3Aclosed) (unreleased) +This release includes fixes and adjustments to the API to make it more usable. Search / List has also been improved with the data that it returns when verbose/detailed, along with info always returning a package with information instead of erroring sometimes. The search results from the community package repository now match what you see on the website. + ### BUG FIXES + * Fix - choco.exe.manifest is ignored because it is extracted AFTER first choco.exe access - see [#1292](https://github.com/chocolatey/choco/issues/1292) * Fix - Chocolatey config changes in 0.10.4+ - The process cannot access the file because it is being used by another process - see [#1241](https://github.com/chocolatey/choco/issues/1241) * Fix - PowerShell sees authenticode hash as changed in scripts that are UTF8 (w/out BOM) that contain unicode characters - see [#1225](https://github.com/chocolatey/choco/issues/1225) * Fix - Chocolatey timed out immediately when execution timeout was infinite (0) - see [#1224](https://github.com/chocolatey/choco/issues/1224) + * Fix - Multiple authenticated sources with same base url fail when authentication is different - see [#1248](https://github.com/chocolatey/choco/issues/1248) * Fix - choco list / search / info - Some packages can't be found - see [#1004](https://github.com/chocolatey/choco/issues/1004) * Fix - chocolatey.config gets corrupted when multiple processes access simultaneously - see [#1258](https://github.com/chocolatey/choco/issues/1258) * Fix - Update ShimGen to 0.8.x to address some issues - see [#1243](https://github.com/chocolatey/choco/issues/1243) + * Fix - AutoUninstaller should skip uninstall keys if they are empty - see [#1315](https://github.com/chocolatey/choco/issues/1315) * Fix - Trace logging should only occur on when trace is enabled - see [#1309](https://github.com/chocolatey/choco/issues/1309) * Fix - RefreshEnv.cmd doesn't set the correct PATH - see [#1227](https://github.com/chocolatey/choco/issues/1227) + * Fix - choco new generates uninstall template with wrong use of registry key variable - see [#1304](https://github.com/chocolatey/choco/issues/1304) * [API] Fix- chocolatey.lib nuget package has incorrect documentation xml - see [#1247](https://github.com/chocolatey/choco/issues/1247) * [API] Fix - Chocolatey file cache still adds a 'chocolatey' directory on each install - see [#1231](https://github.com/chocolatey/choco/issues/1231) * [API] Fix - List and Count should implement similar functionality as run - see [#1298](https://github.com/chocolatey/choco/issues/1298) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83294f0c8c..71b8a113fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,7 @@ The Chocolatey team has very explicit information here regarding the process for - [PowerShell](#powershell) - [Debugging / Testing](#debugging--testing) - [Visual Studio](#visual-studio) + - [Automated Tests](#automated-tests) - [Chocolatey Build](#chocolatey-build) - [Prepare Commits](#prepare-commits) - [Submit Pull Request (PR)](#submit-pull-request-pr) @@ -157,12 +158,54 @@ When you are using Visual Studio, ensure the following: * Use `Debug` configuration - debug configuration keeps your local changes separate from the machine installed Chocolatey. * `chocolatey.console` is the project you are looking to run. * If you make changes to anything that is in `chocolatey.resources`, delete the folder in `chocolatey.console\bin\Debug` that corresponds to where you've made changes as Chocolatey does not automatically detect changes in the files it is extracting from resource manifests. + * The automated testing framework that Chocolatey uses is [NUnit](https://www.nunit.org/), [TinySpec](https://www.nuget.org/packages/TinySpec.NUnit), [Moq](https://www.nuget.org/packages/moq), and [Should](https://www.nuget.org/packages/Should/). Do not be thrown off thinking it using something else based on seeing `Fact` attributes for specs/tests. That is TinySpec. + * For a good understanding of all frameworks used, please see [CREDITS](https://github.com/chocolatey/choco/blob/master/docs/legal/CREDITS.md) + +##### Automated Tests + +> "Testing doesn't prove the absence of bugs, they can only prove code works in the way you've tested." + +The design was to get the testing framework out of the way and make it easy to swap out should it ever need to be (the former is the important goal). We test behaviors of the system, which doesn't simply mean ensuring code coverage. It means we want to see how the system behaves under certain behaviors. As you may see from looking over the tests, we have an interesting way of setting up our specs. We recommend importing the ReSharper templates in `docs\resharper_templates`. This will make adding specs and new spec files quite a bit easier. + +The method of testing as you will see is a file that contains many test classes (scenarios) that set up and perform a behavior, then perform one or more validations (tests/facts) on that scenario. Typically when in a unit test suite, there would be a file for every representative class in the production code. You may not see this as much in this codebase as there are areas that could use more coverage. + +We recognize the need for a very tight feedback loop (running and debugging tests right inside Visual Studio). Some great tools for running and debugging automated tests are [TestDriven.NET](http://www.testdriven.net/) and [ReSharper](https://www.jetbrains.com/resharper/) (you only need one, although both are recommended for development). We recommend TestDriven over other tools as it is absolutely wonderful in what it does. + +With the way the testing framework is designed, it is helpful to gain an understanding on how you can debug into tests. There are a couple of known oddities when it comes to trying to run tests in Visual Studio: + + * You can run a test or tests within a class. + * You can also right click on a folder (and solution folder), a project, or the solution and run tests. + * You can ***not*** click on a file and attempt to run/debug automated tests. You will see the following message: "The target type doesn't contain tests from a known test framework or a 'Main' method." + * You also cannot run all tests within a file by selecting somewhere outside a testing class and attempting to run tests. You will see the message above. + +Some quick notes on testing terminology (still a WIP): + + * Testing - anything done to test, whether manual, automated, or otherwise. + * Automated Testing - Any type of written test that can be run in an automated way, typically in the form of C# tests. + * Spec / Fact / Observation - these are synonyms for a test or validation. + * System Under Test (SUT) - the code or concern you are testing. + * Mock / Fake / Stub / Double - an object that provides a known state back to the system under test when the system under test interacts with other objects. This can be done with unit and whitebox integration testing. This allows for actual unit testing as most units (classes/functions) depend on working with other units (classes/functions) to get or set information and state. While each of [these are slightly different](https://martinfowler.com/articles/mocksArentStubs.html), the basic functionality is that they are standing in for other production code. + * Concern - an area of production code you are testing in e.g. "Concern for AutoUninstallerService". + * Regression Test Suite / Regression Suite - The automated tests that are in the form of code. + * Whitebox Testing - tests where you access the internals of the application. + * Unit Testing - We define a unit as a class and a method in C#. In PowerShell this is per function. If it involves another class or function, you have graduated to an integration. This is where Mocks come in to ensure no outside state is introduced. + * Whitebox Integration Testing - testing anything that is more than a unit. + * System Integration Testing - testing anything that goes out of the bounds of the written production code. Typically when running the code to get or set some state is where you will see this. And yes, even using DateTime.Now counts as system integration testing as it accesses something external to the application. This is why you will see we insulate those calls to something in the application so they can be easily tested against. + * Blackbox Testing - tests where you do not access internals of the application + * Physical Integration Testing - This is where you are testing the application with other components such as config files. + * Blackbox Integration Testing / End to End Testing - This is where you are testing inputs and outputs of the system. + +As far as testing goes, unit tests are extremely quick feedback and great for longer term maintenance, where black box tests give you the most coverage, but are the slowest feedback loops and typically the most frail. Each area of testing has strengths and weaknesses and it's good to understand each of them. + + +**NOTE**: One of the hardest forms of testing is unit testing, as it almost always requires faking out other parts of the system (also known as mocking). #### Chocolatey Build **NOTE:** When you are doing this, we almost always recommend you take the output of the build to another machine to do the testing, like the [Chocolatey Test Environment](https://github.com/chocolatey/chocolatey-test-environment). * Run `build.bat`. + * There is a detailed log of the output in both `build_output` and `code_drop\build_artifacts`. The `build_artifacts` folders contain a lot of detail with each individual tool's output and reporting (helpful when wanting to see a visual of what tests failed). * There are two folders created - `build_output` and `code_drop`. You are looking for `code_drop\chocolatey\console` or `code_drop\nuget`. The `choco.exe` file contains everything it needs, but it does unpack the manifest on first use, so you could run into [#1292](https://github.com/chocolatey/choco/issues/1292). * You will need to pass `--allow-unofficial-build` for it to work when built with release mode. * You can also try `build.debug.bat` - note that it is newer and it may have an issue or two. It doesn't require `--allow-unofficial-build` as the binaries are built for debugging. diff --git a/docs/resharper_templates/Fact_LiveTemplate.DotSettings b/docs/resharper_templates/Fact_LiveTemplate.DotSettings new file mode 100644 index 0000000000..e2f3004835 --- /dev/null +++ b/docs/resharper_templates/Fact_LiveTemplate.DotSettings @@ -0,0 +1,21 @@ + + True + fact + creates a fact + +[Fact] +public void should_$result_in$() +{ + $END$ +} + + + True + True + Imported 4/20/2014 + True + True + InCSharpFile + 2.0 + True + 0 \ No newline at end of file diff --git a/docs/resharper_templates/TinySpecSpec_FileTemplate.DotSettings b/docs/resharper_templates/TinySpecSpec_FileTemplate.DotSettings new file mode 100644 index 0000000000..4ba08e4cc5 --- /dev/null +++ b/docs/resharper_templates/TinySpecSpec_FileTemplate.DotSettings @@ -0,0 +1,67 @@ + + True + TinySpecSpec + namespace $namespace$ +{ + using Moq; + using Should; + + public class $name$ + { + public abstract class $name$Base : TinySpec + { + protected $item_under_test$ itemUnderTest; + + public override void Context() + { + itemUnderTest = new $item_under_test$(); + } + } + + public class when_$item_under_test$_$has_context$ : $name$Base + { + private string result; + + public override void Context() + { + base.Context(); + } + + public override void Because() + { + result = $this_happened$ + } + + [Fact] + public void should_$result_in$() + { + $END$ + } + } + } +} + True + True + Imported 4/20/2014 + cs + Spec + False + True + True + InCSharpProjectFile + True + fileDefaultNamespace() + -1 + 0 + True + getFileNameWithoutExtension() + -1 + 1 + True + 2 + True + 3 + True + 4 + True + 5 \ No newline at end of file diff --git a/nuget/chocolatey/chocolatey.nuspec b/nuget/chocolatey/chocolatey.nuspec index 45cd312cfe..edfc7d3371 100644 --- a/nuget/chocolatey/chocolatey.nuspec +++ b/nuget/chocolatey/chocolatey.nuspec @@ -65,16 +65,22 @@ See all - https://github.com/chocolatey/choco/blob/stable/CHANGELOG.md ## 0.10.6 +This release includes fixes and adjustments to the API to make it more usable. Search / List has also been improved with the data that it returns when verbose/detailed, along with info always returning a package with information instead of erroring sometimes. The search results from the community package repository now match what you see on the website. + ### BUG FIXES + * Fix - choco.exe.manifest is ignored because it is extracted AFTER first choco.exe access - see [#1292](https://github.com/chocolatey/choco/issues/1292) * Fix - Chocolatey config changes in 0.10.4+ - The process cannot access the file because it is being used by another process - see [#1241](https://github.com/chocolatey/choco/issues/1241) * Fix - PowerShell sees authenticode hash as changed in scripts that are UTF8 (w/out BOM) that contain unicode characters - see [#1225](https://github.com/chocolatey/choco/issues/1225) * Fix - Chocolatey timed out immediately when execution timeout was infinite (0) - see [#1224](https://github.com/chocolatey/choco/issues/1224) + * Fix - Multiple authenticated sources with same base url fail when authentication is different - see [#1248](https://github.com/chocolatey/choco/issues/1248) * Fix - choco list / search / info - Some packages can't be found - see [#1004](https://github.com/chocolatey/choco/issues/1004) * Fix - chocolatey.config gets corrupted when multiple processes access simultaneously - see [#1258](https://github.com/chocolatey/choco/issues/1258) * Fix - Update ShimGen to 0.8.x to address some issues - see [#1243](https://github.com/chocolatey/choco/issues/1243) + * Fix - AutoUninstaller should skip uninstall keys if they are empty - see [#1315](https://github.com/chocolatey/choco/issues/1315) * Fix - Trace logging should only occur on when trace is enabled - see [#1309](https://github.com/chocolatey/choco/issues/1309) * Fix - RefreshEnv.cmd doesn't set the correct PATH - see [#1227](https://github.com/chocolatey/choco/issues/1227) + * Fix - choco new generates uninstall template with wrong use of registry key variable - see [#1304](https://github.com/chocolatey/choco/issues/1304) * [API] Fix- chocolatey.lib nuget package has incorrect documentation xml - see [#1247](https://github.com/chocolatey/choco/issues/1247) * [API] Fix - Chocolatey file cache still adds a 'chocolatey' directory on each install - see [#1231](https://github.com/chocolatey/choco/issues/1231) * [API] Fix - List and Count should implement similar functionality as run - see [#1298](https://github.com/chocolatey/choco/issues/1298) diff --git a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs index 3efb1ec0a3..f021c6d521 100644 --- a/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/AutomaticUninstallerServiceSpecs.cs @@ -51,6 +51,7 @@ public abstract class AutomaticUninstallerServiceSpecsBase : TinySpec protected IList registryKeys = new List(); protected IInstaller installerType = new CustomInstaller(); + protected readonly string expectedDisplayName = "WinDirStat"; protected readonly string originalUninstallString = @"""C:\Program Files (x86)\WinDirStat\Uninstall.exe"""; protected readonly string expectedUninstallString = @"C:\Program Files (x86)\WinDirStat\Uninstall.exe"; @@ -69,6 +70,7 @@ public override void Context() registryKeys.Add( new RegistryApplicationKey { + DisplayName = expectedDisplayName, InstallLocation = @"C:\Program Files (x86)\WinDirStat", UninstallString = originalUninstallString, HasQuietUninstall = true, @@ -197,7 +199,7 @@ public override void Because() [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); + MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - '{0}' appears to have been uninstalled already by other means.".format_with(expectedDisplayName)), Times.Once); } [Fact] @@ -219,6 +221,7 @@ public override void Context() registryKeys.Add( new RegistryApplicationKey { + DisplayName = expectedDisplayName, InstallLocation = string.Empty, UninstallString = originalUninstallString, HasQuietUninstall = true, @@ -247,6 +250,46 @@ public void should_call_command_executor() c => c.execute(expectedUninstallString, args, It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Once); } + } + + public class when_uninstall_string_is_empty : AutomaticUninstallerServiceSpecsBase + { + public override void Context() + { + base.Context(); + fileSystem.ResetCalls(); + registryKeys.Clear(); + registryKeys.Add( + new RegistryApplicationKey + { + DisplayName = expectedDisplayName, + InstallLocation = @"C:\Program Files (x86)\WinDirStat", + UninstallString = string.Empty, + HasQuietUninstall = false, + KeyPath = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinDirStat" + }); + packageInformation.RegistrySnapshot = new Registry("123", registryKeys); + fileSystem.Setup(x => x.file_exists(expectedUninstallString)).Returns(true); + } + + 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 - '{0}' does not have an uninstall string.".format_with(expectedDisplayName)), 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>(), It.IsAny()), + Times.Never); + } } public class when_registry_location_does_not_exist : AutomaticUninstallerServiceSpecsBase @@ -266,7 +309,7 @@ public override void Because() [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); + MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - '{0}' appears to have been uninstalled already by other means.".format_with(expectedDisplayName)), Times.Once); } [Fact] @@ -276,8 +319,8 @@ public void should_not_call_command_executor() c => c.execute(It.IsAny(), 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() @@ -298,7 +341,7 @@ public override void Because() [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); + MockLogger.Verify(l => l.Info(" Skipping auto uninstaller - '{0}' appears to have been uninstalled already by other means.".format_with(expectedDisplayName)), Times.Once); } [Fact] diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs index 78bdc12220..d54677f5b9 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetCredentialProvider.cs @@ -19,6 +19,7 @@ namespace chocolatey.infrastructure.app.nuget using System; using System.Linq; using System.Net; + using System.Text.RegularExpressions; using commandline; using NuGet; using configuration; @@ -43,14 +44,18 @@ public ICredentials GetCredentials(Uri uri, IWebProxy proxy, CredentialType cred { throw new ArgumentNullException("uri"); } + if (retrying) { this.Log().Warn("Invalid credentials specified."); } var configSourceUri = new Uri(INVALID_URL); + + this.Log().Debug(ChocolateyLoggers.Verbose, "Attempting to gather credentials for '{0}'".format_with(uri.OriginalString)); try { + // the source to validate against is typically passed in var firstSpecifiedSource = _config.Sources.to_string().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault().to_string(); if (!string.IsNullOrWhiteSpace(firstSpecifiedSource)) { @@ -62,6 +67,7 @@ public ICredentials GetCredentials(Uri uri, IWebProxy proxy, CredentialType cred this.Log().Warn("Cannot determine uri from specified source:{0} {1}".format_with(Environment.NewLine, ex.Message)); } + // did the user pass credentials and a source? if (_config.Sources.TrimEnd('/').is_equal_to(uri.OriginalString.TrimEnd('/')) || configSourceUri.Host.is_equal_to(uri.Host)) { if (!string.IsNullOrWhiteSpace(_config.SourceCommand.Username) && !string.IsNullOrWhiteSpace(_config.SourceCommand.Password)) @@ -72,16 +78,13 @@ public ICredentials GetCredentials(Uri uri, IWebProxy proxy, CredentialType cred } } - var source = _config.MachineSources.FirstOrDefault(s => + // credentials were not explicit + // discover based on closest match in sources + var candidateSources = _config.MachineSources.Where( + s => { var sourceUrl = s.Key.TrimEnd('/'); - var equalAtFullUri = sourceUrl.is_equal_to(uri.OriginalString.TrimEnd('/')) - && !string.IsNullOrWhiteSpace(s.Username) - && !string.IsNullOrWhiteSpace(s.EncryptedPassword); - - if (equalAtFullUri) return true; - try { var sourceUri = new Uri(sourceUrl); @@ -95,15 +98,49 @@ public ICredentials GetCredentials(Uri uri, IWebProxy proxy, CredentialType cred } return false; - }); + }).ToList(); + + MachineSourceConfiguration source = null; + + + if (candidateSources.Count == 1) + { + // only one match, use it + source = candidateSources.FirstOrDefault(); + } + else if (candidateSources.Count > 1) + { + // find the source that is the closest match + foreach (var candidateSource in candidateSources.or_empty_list_if_null()) + { + var candidateRegEx = new Regex(Regex.Escape(candidateSource.Key.TrimEnd('/')),RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + if (candidateRegEx.IsMatch(uri.OriginalString.TrimEnd('/'))) + { + this.Log().Debug("Source selected will be '{0}'".format_with(candidateSource.Key.TrimEnd('/'))); + source = candidateSource; + break; + } + } + + if (source == null && !retrying) + { + // use the first source. If it fails, fall back to grabbing credentials from the user + var candidateSource = candidateSources.First(); + this.Log().Debug("Evaluated {0} candidate sources but was unable to find a match, using {1}".format_with(candidateSources.Count, candidateSource.Key.TrimEnd('/'))); + source = candidateSource; + } + } if (source == null) { + this.Log().Debug("Asking user for credentials for '{0}'".format_with(uri.OriginalString)); return get_credentials_from_user(uri, proxy, credentialType); } - - this.Log().Debug("Using saved credentials"); - + else + { + this.Log().Debug("Using saved credentials"); + } + return new NetworkCredential(source.Username, NugetEncryptionUtility.DecryptString(source.EncryptedPassword)); } diff --git a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs index fd77c14185..b0485ac69f 100644 --- a/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs +++ b/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs @@ -85,11 +85,18 @@ public void run(PackageResult packageResult, ChocolateyConfiguration config) public void remove(RegistryApplicationKey key, ChocolateyConfiguration config, PackageResult packageResult, string packageCacheLocation) { - this.Log().Debug(() => " Preparing uninstall key '{0}'".format_with(key.UninstallString.to_string().escape_curly_braces())); + //todo: if there is a local package, look to use it in the future + if (string.IsNullOrWhiteSpace(key.UninstallString)) + { + this.Log().Info(" Skipping auto uninstaller - '{0}' does not have an uninstall string.".format_with(!string.IsNullOrEmpty(key.DisplayName.to_string()) ? key.DisplayName.to_string().escape_curly_braces() : "The application")); + return; + } + + this.Log().Debug(() => " Preparing uninstall key '{0}' for '{1}'".format_with(key.UninstallString.to_string().escape_curly_braces(), key.DisplayName.to_string().escape_curly_braces())); 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().Info(" Skipping auto uninstaller - '{0}' appears to have been uninstalled already by other means.".format_with(!string.IsNullOrEmpty(key.DisplayName.to_string()) ? key.DisplayName.to_string().escape_curly_braces() : "The application")); this.Log().Debug(() => " Searched for install path '{0}' - found? {1}".format_with(key.InstallLocation.to_string().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.installer_value_exists(key.KeyPath, ApplicationParameters.RegistryValueInstallLocation))); return;