diff --git a/src/chocolatey.resources/helpers/functions/UnInstall-ChocolateyZipPackage.ps1 b/src/chocolatey.resources/helpers/functions/UnInstall-ChocolateyZipPackage.ps1 index 15eeb7ab2c..33c7bb3812 100644 --- a/src/chocolatey.resources/helpers/functions/UnInstall-ChocolateyZipPackage.ps1 +++ b/src/chocolatey.resources/helpers/functions/UnInstall-ChocolateyZipPackage.ps1 @@ -64,7 +64,14 @@ param( Write-FunctionCallLogMessage -Invocation $MyInvocation -Parameters $PSBoundParameters $packagelibPath=$env:chocolateyPackageFolder - $zipContentFile=(join-path $packagelibPath $zipFileName) + ".txt" + $zipContentFile=(join-path $packagelibPath $zipFileName) + "Install.txt" + + # The Zip Content File may have previously existed under a different + # name. If *Install.txt doesn't exist, check for the old name + if(-Not (Test-Path -Path $zipFileContentPath)) { + $zipContentFile=(Join-Path $packagelibPath -ChildPath $zipFileName) + ".txt" + } + if ((Test-Path -path $zipContentFile)) { $zipContentFile $zipContents=get-content $zipContentFile diff --git a/src/chocolatey/infrastructure.app/domain/RegistryValueExtensions.cs b/src/chocolatey/infrastructure.app/domain/RegistryValueExtensions.cs index 7b4fd1efc4..7c7aa9f178 100644 --- a/src/chocolatey/infrastructure.app/domain/RegistryValueExtensions.cs +++ b/src/chocolatey/infrastructure.app/domain/RegistryValueExtensions.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,7 +24,11 @@ public static string get_value_as_string(this RegistryKey key, string name) { if (key == null) return string.Empty; - return key.GetValue(name).to_string().Replace("\0", string.Empty); + // Since it is possible that registry keys contain characters that are not valid + // in XML files, ensure that all content is escaped, prior to serialization + var escapedXml = System.Security.SecurityElement.Escape(key.GetValue(name).to_string()); + + return escapedXml == null ? string.Empty : escapedXml.Replace("\0", string.Empty); } } } diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs index bd98a7f662..80616122b4 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.services { + using System; using System.IO; using System.Text; using NuGet; @@ -29,6 +30,7 @@ public class ChocolateyPackageInformationService : IChocolateyPackageInformation private readonly IRegistryService _registryService; private readonly IFilesService _filesService; private const string REGISTRY_SNAPSHOT_FILE = ".registry"; + private const string REGISTRY_SNAPSHOT_BAD_FILE = ".registry.bad"; private const string FILES_SNAPSHOT_FILE = ".files"; private const string SILENT_UNINSTALLER_FILE = ".silentUninstaller"; private const string SIDE_BY_SIDE_FILE = ".sxs"; @@ -59,28 +61,59 @@ public ChocolateyPackageInformation get_package_information(IPackage package) return packageInformation; } - FaultTolerance.try_catch_with_logging_exception( - () => + var deserializationErrorMessage = @" +A corrupt .registry file exists at {0}. + Open this file in a text editor, and remove/escape any characters that + are regarded as illegal within XML strings not surrounded by CData. + These are typically the characters &, ', "", and `<`, `>`. Again, this + is an XML document, so you will see many < and > characters, so just + focus exclusively in the string values not surrounded by CData. Once + these have been corrected, rename the .registry.bad file to .registry. + Once saved, try running the same Chocolatey command that was just + executed, so verify problem is fixed. + NOTE: It will not be possible to rename the file in Windows Explorer. + Instead, you can use the following PowerShell command: + Move-Item .\.registry.bad .\.registry +".format_with(_fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_BAD_FILE)); + try + { + if (_fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_BAD_FILE))) + { + this.Log().Warn(deserializationErrorMessage); + } + else + { + packageInformation.RegistrySnapshot = _registryService.read_from_file(_fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE)); + } + } + catch (Exception) + { + FaultTolerance.try_catch_with_logging_exception( + () => { - packageInformation.RegistrySnapshot = _registryService.read_from_file(_fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE)); - }, - "Unable to read registry snapshot file for {0} (located at {1})".format_with(package.Id, _fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE)), + this.Log().Warn(deserializationErrorMessage); + + // rename the bad registry file so that it isn't processed again + _fileSystem.move_file(_fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE), _fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_BAD_FILE)); + }, + "Unable to read registry snapshot file for {0} (located at {1})".format_with(package.Id, _fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE)), throwError: false, logWarningInsteadOfError: true, isSilent: true - ); - + ); + } + FaultTolerance.try_catch_with_logging_exception( () => { - packageInformation.FilesSnapshot = _filesService.read_from_file(_fileSystem.combine_paths(pkgStorePath, FILES_SNAPSHOT_FILE)); - }, - "Unable to read files snapshot file", - throwError: false, - logWarningInsteadOfError: true, + packageInformation.FilesSnapshot = _filesService.read_from_file(_fileSystem.combine_paths(pkgStorePath, FILES_SNAPSHOT_FILE)); + }, + "Unable to read files snapshot file", + throwError: false, + logWarningInsteadOfError: true, isSilent:true ); - + packageInformation.HasSilentUninstall = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, SILENT_UNINSTALLER_FILE)); packageInformation.IsSideBySide = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, SIDE_BY_SIDE_FILE)); packageInformation.IsPinned = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, PIN_FILE)); @@ -97,9 +130,9 @@ public ChocolateyPackageInformation get_package_information(IPackage package) () => { packageInformation.VersionOverride = new SemanticVersion(_fileSystem.read_file(versionOverrideFile).trim_safe()); - }, - "Unable to read version override file", - throwError: false, + }, + "Unable to read version override file", + throwError: false, logWarningInsteadOfError: true ); } @@ -148,8 +181,8 @@ public void save_package_information(ChocolateyPackageInformation packageInforma else { _fileSystem.delete_file(_fileSystem.combine_paths(pkgStorePath, ARGS_FILE)); - } - + } + if (!string.IsNullOrWhiteSpace(packageInformation.ExtraInformation)) { var extraFile = _fileSystem.combine_paths(pkgStorePath, EXTRA_FILE); @@ -160,7 +193,7 @@ public void save_package_information(ChocolateyPackageInformation packageInforma { _fileSystem.delete_file(_fileSystem.combine_paths(pkgStorePath, EXTRA_FILE)); } - + if (packageInformation.VersionOverride != null) { var versionOverrideFile = _fileSystem.combine_paths(pkgStorePath, VERSION_OVERRIDE_FILE); @@ -192,7 +225,7 @@ public void save_package_information(ChocolateyPackageInformation packageInforma else { _fileSystem.delete_file(_fileSystem.combine_paths(pkgStorePath, PIN_FILE)); - } + } } public void remove_package_information(IPackage package) diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index 3d6b65bca4..8ea789c9b2 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -253,7 +253,7 @@ public void evaluate_keys(RegistryKey key, Registry snapshot) private void get_msi_information(RegistryApplicationKey appKey, RegistryKey key) { _componentLoopCount = 0; - + var userDataProductKeyId = get_msi_user_data_key(key.Name); if (string.IsNullOrWhiteSpace(userDataProductKeyId)) return; @@ -398,7 +398,7 @@ public Registry read_from_file(string filePath) return null; } - return _xmlService.deserialize(filePath); + return _xmlService.deserialize(filePath, 1); } private void get_values(RegistryKey key, string subKeyName, IList values, bool expandValues) @@ -530,4 +530,4 @@ public static GenericRegistryValue get_value(RegistryHiveType hive, string subKe } } -} \ No newline at end of file +} diff --git a/src/chocolatey/infrastructure/services/IXmlService.cs b/src/chocolatey/infrastructure/services/IXmlService.cs index 13f693c0cb..73cbd75678 100644 --- a/src/chocolatey/infrastructure/services/IXmlService.cs +++ b/src/chocolatey/infrastructure/services/IXmlService.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,6 +26,15 @@ public interface IXmlService /// XmlType deserialize(string xmlFilePath); + /// + /// Deserializes the specified XML file path. + /// + /// The type of the ml type. + /// The XML file path. + /// The number of times to attempt deserialization on event of a failure. + /// + XmlType deserialize(string xmlFilePath, int retryCount); + /// /// Serializes the specified XML type. /// diff --git a/src/chocolatey/infrastructure/services/XmlService.cs b/src/chocolatey/infrastructure/services/XmlService.cs index 6d8f4ef159..cd5e93e7dd 100644 --- a/src/chocolatey/infrastructure/services/XmlService.cs +++ b/src/chocolatey/infrastructure/services/XmlService.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -45,75 +45,81 @@ public XmlService(IFileSystem fileSystem, IHashProvider hashProvider) public XmlType deserialize(string xmlFilePath) { - return FaultTolerance.retry(3, () => GlobalMutex.enter( - () => - { - this.Log().Trace("Entered mutex to deserialize '{0}'".format_with(xmlFilePath)); - - return FaultTolerance.try_catch_with_logging_exception( - () => - { - var xmlSerializer = new XmlSerializer(typeof(XmlType)); - using (var fileStream = _fileSystem.open_file_readonly(xmlFilePath)) - using (var fileReader = new StreamReader(fileStream)) - using (var xmlReader = XmlReader.Create(fileReader)) - { - if (!xmlSerializer.CanDeserialize(xmlReader)) - { - this.Log().Warn("Cannot deserialize response of type {0}", typeof(XmlType)); - return default(XmlType); - } - - try - { - return (XmlType)xmlSerializer.Deserialize(xmlReader); - } - catch (InvalidOperationException ex) - { - // Check if its just a malformed document. - if (ex.Message.Contains("There is an error in XML document")) - { - // If so, check for a backup file and try an parse that. - if (_fileSystem.file_exists(xmlFilePath + ".backup")) - { - using (var backupStream = _fileSystem.open_file_readonly(xmlFilePath + ".backup")) - using (var backupReader = new StreamReader(backupStream)) - using (var backupXmlReader = XmlReader.Create(backupReader)) - { - var validConfig = (XmlType)xmlSerializer.Deserialize(backupXmlReader); - - // If there's no errors and it's valid, go ahead and replace the bad file with the backup. - if (validConfig != null) - { - _fileSystem.copy_file(xmlFilePath + ".backup", xmlFilePath, overwriteExisting: true); - } - - return validConfig; - } - } - } - - throw; - } - finally - { - foreach (var updateFile in _fileSystem.get_files(_fileSystem.get_directory_name(xmlFilePath), "*.update").or_empty_list_if_null()) - { - this.Log().Debug("Removing '{0}'".format_with(updateFile)); - FaultTolerance.try_catch_with_logging_exception( - () => _fileSystem.delete_file(updateFile), - errorMessage: "Unable to remove update file", - logDebugInsteadOfError: true, - isSilent: true - ); - } - } - } - }, - "Error deserializing response of type {0}".format_with(typeof(XmlType)), - throwError: true); + return deserialize(xmlFilePath, 3); + } - }, MUTEX_TIMEOUT), + public XmlType deserialize(string xmlFilePath, int retryCount) + { + return FaultTolerance.retry(retryCount, () => GlobalMutex.enter( + () => + { + this.Log().Trace("Entered mutex to deserialize '{0}'".format_with(xmlFilePath)); + + return FaultTolerance.try_catch_with_logging_exception( + () => + { + var xmlSerializer = new XmlSerializer(typeof(XmlType)); + using (var fileStream = _fileSystem.open_file_readonly(xmlFilePath)) + using (var fileReader = new StreamReader(fileStream)) + using (var xmlReader = XmlReader.Create(fileReader)) + { + if (!xmlSerializer.CanDeserialize(xmlReader)) + { + this.Log().Warn("Cannot deserialize response of type {0}", typeof(XmlType)); + return default(XmlType); + } + + try + { + return (XmlType)xmlSerializer.Deserialize(xmlReader); + } + catch (InvalidOperationException ex) + { + // Check if its just a malformed document. + if (ex.Message.Contains("There is an error in XML document")) + { + // If so, check for a backup file and try an parse that. + if (_fileSystem.file_exists(xmlFilePath + ".backup")) + { + using (var backupStream = _fileSystem.open_file_readonly(xmlFilePath + ".backup")) + using (var backupReader = new StreamReader(backupStream)) + using (var backupXmlReader = XmlReader.Create(backupReader)) + { + var validConfig = (XmlType)xmlSerializer.Deserialize(backupXmlReader); + + // If there's no errors and it's valid, go ahead and replace the bad file with the backup. + if (validConfig != null) + { + _fileSystem.copy_file(xmlFilePath + ".backup", xmlFilePath, overwriteExisting: true); + } + + return validConfig; + } + } + } + + throw; + + } + finally + { + foreach (var updateFile in _fileSystem.get_files(_fileSystem.get_directory_name(xmlFilePath), "*.update").or_empty_list_if_null()) + { + this.Log().Debug("Removing '{0}'".format_with(updateFile)); + FaultTolerance.try_catch_with_logging_exception( + () => _fileSystem.delete_file(updateFile), + errorMessage: "Unable to remove update file", + logDebugInsteadOfError: true, + isSilent: true + ); + } + } + } + }, + "Error deserializing response of type {0}".format_with(typeof(XmlType)), + throwError: true); + + }, MUTEX_TIMEOUT), waitDurationMilliseconds: 200, increaseRetryByMilliseconds: 200); }