diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
index 5a36efb131..549ac89cb8 100644
--- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
+++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
@@ -30,6 +30,9 @@ namespace chocolatey.infrastructure.app.configuration
[Serializable]
public class ChocolateyConfiguration
{
+ [NonSerialized]
+ private ChocolateyConfiguration _originalConfiguration;
+
public ChocolateyConfiguration()
{
RegularOutput = true;
@@ -58,6 +61,86 @@ public ChocolateyConfiguration()
#endif
}
+ ///
+ /// Creates a backup of the current version of the configuration class.
+ ///
+ /// One or more objects in the class or child classes are not serializable.
+ public void start_backup()
+ {
+ // We do this the easy way to ensure that we have a clean copy
+ // of the original configuration file.
+ _originalConfiguration = this.deep_copy();
+ }
+
+ ///
+ /// Restore the backup that has previously been created to the initial
+ /// state, without making the class reference types the same to prevent
+ /// the initial configuration class being updated at the same time if a
+ /// value changes.
+ ///
+ /// Whether a backup that was previously made should be removed after resetting the configuration.
+ /// No backup has been created before trying to reset the current configuration, and removal of the backup was not requested.
+ ///
+ /// This call may make quite a lot of allocations on the Gen0 heap, as such
+ /// it is best to keep the calls to this method at a minimum.
+ ///
+ public void reset_config(bool removeBackup = false)
+ {
+ if (_originalConfiguration == null)
+ {
+ if (removeBackup)
+ {
+ // If we will also be removing the backup, we do not care if it is already
+ // null or not, as that is the intended state when this method returns.
+ return;
+ }
+
+ throw new InvalidOperationException("No backup has been created before trying to reset the current configuration, and removal of the backup was not requested.");
+ }
+
+ var t = this.GetType();
+
+ foreach (var property in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ try
+ {
+ var originalValue = property.GetValue(_originalConfiguration, new object[0]);
+
+ if (removeBackup || property.DeclaringType.IsPrimitive || property.DeclaringType.IsValueType || property.DeclaringType == typeof(string))
+ {
+ // If the property is a primitive, a value type or a string, then a copy of the value
+ // will be created by the .NET Runtime automatically, and we do not need to create a deep clone of the value.
+ // Additionally, if we will be removing the backup there is no need to create a deep copy
+ // for any reference types, as such we also set the reference itself so it is not needed
+ // to allocate more memory.
+ property.SetValue(this, originalValue, new object[0]);
+ }
+ else if (originalValue != null)
+ {
+ // We need to do a deep copy of the value so it won't copy the reference itself,
+ // but rather the actual values we are interested in.
+ property.SetValue(this, originalValue.deep_copy(), new object[0]);
+ }
+ else
+ {
+ property.SetValue(this, null, new object[0]);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationException("Unable to restore the value for the property '{0}'.".format_with(property.Name), ex);
+ }
+ }
+
+ if (removeBackup)
+ {
+ // It is enough to set the original configuration to null to
+ // allow GC to clean it up the next time it runs on the stored Generation
+ // Heap Table.
+ _originalConfiguration = null;
+ }
+ }
+
// overrides
public override string ToString()
{
@@ -237,6 +320,7 @@ private void append_output(StringBuilder propertyValues, string append)
[Obsolete("Side by Side installation is deprecated, and is pending removal in v2.0.0")]
public bool AllowMultipleVersions { get; set; }
+
public bool AllowDowngrade { get; set; }
public bool ForceDependencies { get; set; }
public string DownloadChecksum { get; set; }