/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/EnvironmentVariables/EnvironmentVariablesUILib/ViewModels/MainViewModel.cs
466 строк
18 KB
Dave Rayment
[EnvironmentVariables] Validation fixes, centralised validation, error message improvements (#46837)
05 авг 2026, 11:42
Не верифицирован
05 авг 2026, 11:42
3079a3c
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using EnvironmentVariablesUILib.Helpers; using EnvironmentVariablesUILib.Models; using EnvironmentVariablesUILib.Telemetry; using Microsoft.UI.Dispatching; namespace EnvironmentVariablesUILib.ViewModels { public partial class MainViewModel : ObservableObject { private readonly IEnvironmentVariablesService _environmentVariablesService; private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); public DefaultVariablesSet UserDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.UserGuid, ResourceLoaderInstance.ResourceLoader.GetString("User"), VariablesSetType.User); public DefaultVariablesSet SystemDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.SystemGuid, ResourceLoaderInstance.ResourceLoader.GetString("System"), VariablesSetType.System); public VariablesSet DefaultVariables { get; private set; } = new DefaultVariablesSet(Guid.NewGuid(), "DefaultVariables", VariablesSetType.User); [ObservableProperty] private ObservableCollection<ProfileVariablesSet> _profiles; [ObservableProperty] private ObservableCollection<Variable> _appliedVariables = new ObservableCollection<Variable>(); [ObservableProperty] private bool _isElevated; [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsInfoBarButtonVisible))] private EnvironmentState _environmentState; public bool IsInfoBarButtonVisible => EnvironmentState == EnvironmentState.EnvironmentMessageReceived; public ProfileVariablesSet AppliedProfile { get; set; } public MainViewModel(IElevationHelper elevationHelper, IEnvironmentVariablesService environmentVariablesService, ILogger logger, ITelemetry telemetry) { _environmentVariablesService = environmentVariablesService; ElevationHelper.ElevationHelperInstance = elevationHelper; LoggerInstance.Logger = logger; TelemetryInstance.Telemetry = telemetry; var isElevated = ElevationHelper.ElevationHelperInstance.IsElevated; IsElevated = isElevated; } private void LoadDefaultVariables() { UserDefaultSet.Variables.Clear(); SystemDefaultSet.Variables.Clear(); DefaultVariables.Variables.Clear(); EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.Machine, SystemDefaultSet); EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.User, UserDefaultSet); foreach (var variable in UserDefaultSet.Variables) { DefaultVariables.Variables.Add(variable); if (AppliedProfile != null) { // This check only drives the "applied from profile" UI state for an existing // user variable. It intentionally uses a looser value comparison than // EntriesEqual: if the current value differs from the profile only by casing, // we still want to treat it as profile-applied for display purposes. bool isDirectlyApplied = AppliedProfile.Variables.Any(x => EnvironmentVariableComparisonHelper.NamesEqual(x.Name, variable.Name) && x.Values.Equals(variable.Values, StringComparison.OrdinalIgnoreCase)); // When a profile overrides an existing user variable, the original user entry // is renamed to "<name>_PowerToys_<profileName>" and kept as a backup. Detect // those renamed backup entries so they can also be marked as profile-applied. bool isDisplacedToBackup = AppliedProfile.Variables.Any(x => EnvironmentVariableComparisonHelper.NamesEqual( variable.Name, EnvironmentVariablesHelper.GetBackupVariableName(x, AppliedProfile.Name))); if (isDirectlyApplied || isDisplacedToBackup) { variable.IsAppliedFromProfile = true; } } } foreach (var variable in SystemDefaultSet.Variables) { DefaultVariables.Variables.Add(variable); } } public void LoadEnvironmentVariables() { LoadDefaultVariables(); LoadProfiles(); PopulateAppliedVariables(); } private void LoadProfiles() { try { var profiles = _environmentVariablesService.ReadProfiles(); foreach (var profile in profiles) { profile.PropertyChanged += Profile_PropertyChanged; foreach (var variable in profile.Variables) { variable.ParentType = VariablesSetType.Profile; } } var appliedProfiles = profiles.Where(x => x.IsEnabled).ToList(); if (appliedProfiles.Count > 0) { var appliedProfile = appliedProfiles.First(); if (!appliedProfile.Valid) { EnvironmentState = EnvironmentState.ProfileNameInvalid; appliedProfile.IsEnabled = false; } else if (!appliedProfile.IsApplicable()) { EnvironmentState = EnvironmentState.ProfileNotApplicable; appliedProfile.IsEnabled = false; } else if (appliedProfile.IsCorrectlyApplied()) { AppliedProfile = appliedProfile; EnvironmentState = EnvironmentState.Unchanged; } else { EnvironmentState = EnvironmentState.ChangedOnStartup; appliedProfile.IsEnabled = false; } } Profiles = new ObservableCollection<ProfileVariablesSet>(profiles); } catch (Exception ex) { // Show some error LoggerInstance.Logger.LogError("Failed to load profiles.json file", ex); Profiles = new ObservableCollection<ProfileVariablesSet>(); } } private void PopulateAppliedVariables() { LoadDefaultVariables(); var variables = new List<Variable>(); if (AppliedProfile != null) { variables = variables.Concat(AppliedProfile.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.Profile)).OrderBy(x => x.Name)).ToList(); } // Variables are expanded to be shown in the applied variables section, so the user sees their actual values. variables = variables.Concat(UserDefaultSet.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.User)).OrderBy(x => x.Name)) .Concat(SystemDefaultSet.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.System)).OrderBy(x => x.Name)) .ToList(); // Handle PATH variable - add USER value to the end of the SYSTEM value var profilePath = variables.FirstOrDefault(x => EnvironmentVariableComparisonHelper.NamesEqual(x.Name, "PATH") && x.ParentType == VariablesSetType.Profile); var userPath = variables.FirstOrDefault(x => EnvironmentVariableComparisonHelper.NamesEqual(x.Name, "PATH") && x.ParentType == VariablesSetType.User); var systemPath = variables.FirstOrDefault(x => EnvironmentVariableComparisonHelper.NamesEqual(x.Name, "PATH") && x.ParentType == VariablesSetType.System); if (systemPath != null) { var clone = systemPath.Clone(); clone.ParentType = VariablesSetType.Path; if (userPath != null) { clone.Values += ";" + userPath.Values; variables.Remove(userPath); } if (profilePath != null) { variables.Remove(profilePath); } variables.Insert(variables.IndexOf(systemPath), clone); variables.Remove(systemPath); } // NB: we treat names case-insensitively when authoring and flagging conflicts, but we // do not de-deupe loaded entries here because they may still contain multiple variables // whose names differ only by case, and the user still needs to review/delete each entry. var duplicates = EnvironmentVariableComparisonHelper.GetDuplicateNameGroups( variables.Where(x => !EnvironmentVariableComparisonHelper.NamesEqual(x.Name, "PATH"))); foreach (var duplicate in duplicates) { var userVar = duplicate.ElementAt(0); var systemVar = duplicate.ElementAt(1); var clone = userVar.Clone(); clone.ParentType = VariablesSetType.Duplicate; clone.Name = systemVar.Name; variables.Insert(variables.IndexOf(userVar), clone); variables.Remove(userVar); variables.Remove(systemVar); } variables = variables.OrderBy(x => x.ParentType).ToList(); AppliedVariables = new ObservableCollection<Variable>(variables); } internal void AddDefaultVariable(Variable variable, VariablesSetType type) { if (!EnvironmentVariablesHelper.SetVariable(variable)) { return; } if (type == VariablesSetType.User) { UserDefaultSet.Variables.Add(variable); UserDefaultSet.Variables = new ObservableCollection<Variable>(UserDefaultSet.Variables.OrderBy(x => x.Name).ToList()); } else if (type == VariablesSetType.System) { SystemDefaultSet.Variables.Add(variable); SystemDefaultSet.Variables = new ObservableCollection<Variable>(SystemDefaultSet.Variables.OrderBy(x => x.Name).ToList()); } PopulateAppliedVariables(); } internal void EditVariable(Variable original, Variable edited, ProfileVariablesSet variablesSet) { bool propagateChange = variablesSet == null /* not a profile */ || variablesSet.Id.Equals(AppliedProfile?.Id); bool changed = original.Name != edited.Name || original.Values != edited.Values; if (changed) { var task = original.Update(edited, propagateChange, variablesSet); task.ContinueWith(x => { _dispatcherQueue.TryEnqueue(() => { PopulateAppliedVariables(); }); }); TelemetryInstance.Telemetry.LogEnvironmentVariablesVariableChangedEvent(original.ParentType); _ = Task.Run(SaveAsync); } } internal void AddProfile(ProfileVariablesSet profile) { profile.PropertyChanged += Profile_PropertyChanged; if (profile.IsEnabled) { UnsetAppliedProfile(); SetAppliedProfile(profile); } Profiles.Add(profile); _ = Task.Run(SaveAsync); } internal void UpdateProfile(ProfileVariablesSet updatedProfile) { var existingProfile = Profiles.Where(x => x.Id == updatedProfile.Id).FirstOrDefault(); if (existingProfile != null) { if (updatedProfile.IsEnabled) { // Let's unset the profile before applying the update. Even if this one is the one that's currently set. UnsetAppliedProfile(); } existingProfile.Name = updatedProfile.Name; existingProfile.IsEnabled = updatedProfile.IsEnabled; existingProfile.Variables = updatedProfile.Variables; } _ = Task.Run(SaveAsync); } private async Task SaveAsync() { try { await _environmentVariablesService.WriteAsync(Profiles); } catch (Exception ex) { // Show some error LoggerInstance.Logger.LogError("Failed to save to profiles.json file", ex); } } private void Profile_PropertyChanged(object sender, PropertyChangedEventArgs e) { var profile = sender as ProfileVariablesSet; if (profile != null) { if (e.PropertyName == nameof(ProfileVariablesSet.IsEnabled)) { if (profile.IsEnabled) { UnsetAppliedProfile(); SetAppliedProfile(profile); TelemetryInstance.Telemetry.LogEnvironmentVariablesProfileEnabledEvent(true); } else { UnsetAppliedProfile(); TelemetryInstance.Telemetry.LogEnvironmentVariablesProfileEnabledEvent(false); } } } _ = Task.Run(SaveAsync); } private void SetAppliedProfile(ProfileVariablesSet profile) { if (profile != null) { if (!profile.Valid) { profile.PropertyChanged -= Profile_PropertyChanged; profile.IsEnabled = false; profile.PropertyChanged += Profile_PropertyChanged; EnvironmentState = EnvironmentState.ProfileNameInvalid; return; } if (!profile.IsApplicable()) { profile.PropertyChanged -= Profile_PropertyChanged; profile.IsEnabled = false; profile.PropertyChanged += Profile_PropertyChanged; EnvironmentState = EnvironmentState.ProfileNotApplicable; return; } } var task = profile.Apply(); task.ContinueWith((a) => { _dispatcherQueue.TryEnqueue(() => { PopulateAppliedVariables(); }); }); AppliedProfile = profile; } private void UnsetAppliedProfile() { if (AppliedProfile != null) { var appliedProfile = AppliedProfile; appliedProfile.PropertyChanged -= Profile_PropertyChanged; var task = AppliedProfile.UnApply(); task.ContinueWith((a) => { _dispatcherQueue.TryEnqueue(() => { PopulateAppliedVariables(); }); }); AppliedProfile.IsEnabled = false; AppliedProfile = null; appliedProfile.PropertyChanged += Profile_PropertyChanged; } } internal void RemoveProfile(ProfileVariablesSet profile) { if (profile.IsEnabled) { UnsetAppliedProfile(); } Profiles.Remove(profile); _ = Task.Run(SaveAsync); } internal void DeleteVariable(Variable variable, ProfileVariablesSet profile) { bool propagateChange = true; if (profile != null) { // Profile variable profile.Variables.Remove(variable); if (!profile.IsEnabled) { propagateChange = false; } _ = Task.Run(SaveAsync); } else { if (variable.ParentType == VariablesSetType.User) { UserDefaultSet.Variables.Remove(variable); } else if (variable.ParentType == VariablesSetType.System) { SystemDefaultSet.Variables.Remove(variable); } } if (propagateChange) { var task = Task.Run(() => { if (profile == null) { EnvironmentVariablesHelper.UnsetVariable(variable); } else { profile.UnapplyVariable(variable); } }); task.ContinueWith((a) => { _dispatcherQueue.TryEnqueue(() => { PopulateAppliedVariables(); }); }); } } } }