Variable.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Collections.ObjectModel; 8 using System.Collections.Specialized; 9 using System.Linq; 10 using System.Text.Json.Serialization; 11 using System.Threading.Tasks; 12 13 using CommunityToolkit.Mvvm.ComponentModel; 14 using EnvironmentVariablesUILib.Helpers; 15 16 namespace EnvironmentVariablesUILib.Models 17 { 18 public partial class Variable : ObservableObject, IJsonOnDeserialized 19 { 20 [ObservableProperty] 21 [NotifyPropertyChangedFor(nameof(Valid))] 22 [NotifyPropertyChangedFor(nameof(ShowAsList))] 23 private string _name; 24 25 [ObservableProperty] 26 private string _values; 27 28 [ObservableProperty] 29 private bool _applyToSystem; 30 31 [JsonIgnore] 32 [property: JsonIgnore] 33 [ObservableProperty] 34 private bool _isAppliedFromProfile; // Used to mark that a variable in a default set is applied by a profile. Used to disable editing / mark it in the UI. 35 36 [JsonIgnore] 37 public bool IsEditable 38 { 39 get 40 { 41 return (ParentType != VariablesSetType.System || ElevationHelper.ElevationHelperInstance.IsElevated) && !IsAppliedFromProfile; 42 } 43 } 44 45 [JsonIgnore] 46 public VariablesSetType ParentType { get; set; } 47 48 // To store the strings in the Values List with actual objects that can be referenced and identity compared 49 public class ValuesListItem 50 { 51 public string Text { get; set; } 52 } 53 54 [ObservableProperty] 55 [property: JsonIgnore] 56 [JsonIgnore] 57 private ObservableCollection<ValuesListItem> _valuesList; 58 59 [JsonIgnore] 60 public bool Valid => Validate(); 61 62 [JsonIgnore] 63 public bool ShowAsList => IsList(); 64 65 private bool IsList() 66 { 67 List<string> listVariables = new() 68 { 69 "_NT_ALT_SYMBOL_PATH", 70 "_NT_SYMBOL_PATH", 71 "_NT_SYMCACHE_PATH", 72 "PATH", 73 "PATHEXT", 74 "PSMODULEPATH", 75 }; 76 77 foreach (var name in listVariables) 78 { 79 if (Name.Equals(name, StringComparison.OrdinalIgnoreCase)) 80 { 81 return true; 82 } 83 } 84 85 return false; 86 } 87 88 public void OnDeserialized() 89 { 90 // No need to save ValuesList to the Json, so we are generating it after deserializing 91 ValuesList = ValuesStringToValuesListItemCollection(Values); 92 } 93 94 public Variable() 95 { 96 } 97 98 public Variable(string name, string values, VariablesSetType parentType) 99 { 100 Name = name; 101 Values = values; 102 ParentType = parentType; 103 104 ValuesList = ValuesStringToValuesListItemCollection(Values); 105 } 106 107 internal static ObservableCollection<ValuesListItem> ValuesStringToValuesListItemCollection(string values) 108 { 109 return new ObservableCollection<ValuesListItem>(values.Split(';').Select(x => new ValuesListItem { Text = x })); 110 } 111 112 internal Task Update(Variable edited, bool propagateChange, ProfileVariablesSet parentProfile) 113 { 114 bool nameChanged = Name != edited.Name; 115 116 var clone = this.Clone(); 117 118 // Update state 119 Name = edited.Name; 120 Values = edited.Values; 121 122 ValuesList = ValuesStringToValuesListItemCollection(Values); 123 124 return Task.Run(() => 125 { 126 // Apply changes 127 if (propagateChange) 128 { 129 if (nameChanged) 130 { 131 if (!EnvironmentVariablesHelper.UnsetVariable(clone)) 132 { 133 LoggerInstance.Logger.LogError("Failed to unset original variable."); 134 } 135 136 if (parentProfile != null) 137 { 138 var backupName = EnvironmentVariablesHelper.GetBackupVariableName(clone, parentProfile.Name); 139 140 // Get backup variable if it exist 141 var backupVariable = EnvironmentVariablesHelper.GetExisting(backupName); 142 if (backupVariable != null) 143 { 144 var variableToRestore = new Variable(clone.Name, backupVariable.Values, backupVariable.ParentType); 145 146 if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(backupVariable)) 147 { 148 LoggerInstance.Logger.LogError("Failed to unset backup variable."); 149 } 150 151 if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToRestore)) 152 { 153 LoggerInstance.Logger.LogError("Failed to restore backup variable."); 154 } 155 } 156 } 157 } 158 159 // Get existing variable with the same name if it exist 160 var variableToOverride = EnvironmentVariablesHelper.GetExisting(Name); 161 162 // It exists. Rename it to preserve it. 163 if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User && parentProfile != null) 164 { 165 // Gets which name the backup variable should have. 166 variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, parentProfile.Name); 167 168 // Only create a backup variable if there's not one already, to avoid overriding. (solves Path nuking errors, for example, after editing path on an enabled profile) 169 if (EnvironmentVariablesHelper.GetExisting(variableToOverride.Name) == null) 170 { 171 // Backup the variable 172 if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToOverride)) 173 { 174 LoggerInstance.Logger.LogError("Failed to set backup variable."); 175 } 176 } 177 } 178 179 if (!EnvironmentVariablesHelper.SetVariable(this)) 180 { 181 LoggerInstance.Logger.LogError("Failed to set new variable."); 182 } 183 } 184 }); 185 } 186 187 internal Variable Clone(bool profile = false) 188 { 189 return new Variable 190 { 191 Name = Name, 192 Values = Values, 193 ParentType = profile ? VariablesSetType.Profile : ParentType, 194 ValuesList = ValuesStringToValuesListItemCollection(Values), 195 }; 196 } 197 198 public bool Validate() 199 { 200 if (string.IsNullOrWhiteSpace(Name)) 201 { 202 return false; 203 } 204 205 const int MaxUserEnvVariableLength = 255; // User-wide env vars stored in the registry have names limited to 255 chars 206 if (ParentType != VariablesSetType.System && Name.Length >= MaxUserEnvVariableLength) 207 { 208 LoggerInstance.Logger.LogError("Variable name too long."); 209 return false; 210 } 211 212 return true; 213 } 214 } 215 }