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  }