Add profile file location management with policy support and rename folder location properties#3340
Add profile file location management with policy support and rename folder location properties#3340
Conversation
…older location properties Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request adds profile folder location management (user-configurable with restart + admin policy override) analogous to the existing settings folder location feature, and renames policy/local-settings JSON properties to consistently follow the Section_SettingName convention.
Changes:
- Implement policy/custom/default resolution + validation for the profiles folder path.
- Update the Profiles settings UI to allow editing/browsing/drag-drop, with policy/portable-mode disabling and restart-on-change flows.
- Rename policy/local-settings JSON property names (
SettingsFolderLocation→Settings_FolderLocation,ProfilesFolderLocation→Profiles_FolderLocation) and update docs/examples accordingly.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| Website/docs/system-wide-policies.md | Updates policy example JSON key for settings folder location. |
| Website/docs/settings/settings.md | Updates documented policy property name for settings folder location. |
| Website/docs/settings/profiles.md | Documents new profiles folder location system-wide policy with examples. |
| Website/docs/changelog/next-release.md | Adds changelog entries for new profiles folder location capability/policy. |
| Source/NETworkManager/Views/SettingsProfilesView.xaml.cs | Adds drag/drop handlers for the profiles location textbox. |
| Source/NETworkManager/Views/SettingsProfilesView.xaml | Reworks the Profiles location UI (editable path, browse button, policy/portable indicators, change/restore actions). |
| Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs | Updates references to renamed settings policy/local-settings property. |
| Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs | Adds Profiles location state + commands for browse/change/restore with restart flow. |
| Source/NETworkManager.Settings/config.json.example | Updates example config to new key names and adds profiles folder location key. |
| Source/NETworkManager.Settings/SettingsManager.cs | Renames settings folder location policy/local-settings references + log messages. |
| Source/NETworkManager.Settings/SettingsInfo.cs | Adds persisted user setting for custom profiles folder location. |
| Source/NETworkManager.Settings/PolicyManager.cs | Logs new policy property names (settings + profiles). |
| Source/NETworkManager.Settings/PolicyInfo.cs | Renames settings policy JSON key and adds profiles policy JSON key. |
| Source/NETworkManager.Settings/LocalSettingsInfo.cs | Renames local-settings property for settings folder location. |
| Source/NETworkManager.Profiles/ProfileManager.cs | Implements policy/custom/default resolution + validation for profiles folder location. |
| Source/NETworkManager.Localization/Resources/Strings.resx | Adds localized strings for profiles location change/restore confirmation dialogs. |
| Source/NETworkManager.Localization/Resources/Strings.Designer.cs | Updates designer for the new localization keys. |
Files not reviewed (1)
- Source/NETworkManager.Localization/Resources/Strings.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| var files = (string[])e.Data.GetData(DataFormats.FileDrop); | ||
|
|
||
| if (files != null) |
There was a problem hiding this comment.
files[0] is accessed without checking files.Length > 0. This can throw if the drop data contains an empty array. Consider guarding for null/empty and, if a file is dropped, using its directory (or rejecting files) so the location textbox doesn't get set to a file path.
| if (files != null) | |
| if (files is { Length: > 0 }) |
|
|
||
| private void TextBoxLocation_PreviewDragOver(object sender, DragEventArgs e) | ||
| { | ||
| e.Effects = DragDropEffects.Copy; |
There was a problem hiding this comment.
PreviewDragOver always sets Copy even when the dragged data isn't a file/folder drop. This makes the UI indicate a valid drop when it isn't. Consider setting Effects=None unless e.Data.GetDataPresent(DataFormats.FileDrop) is true.
| e.Effects = DragDropEffects.Copy; | |
| e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) | |
| ? DragDropEffects.Copy | |
| : DragDropEffects.None; |
| <UserControl.Resources> | ||
| <converters:BooleanToVisibilityCollapsedConverter x:Key="BooleanToVisibilityCollapsedConverter" /> | ||
| <converters:BooleanReverseToVisibilityCollapsedConverter x:Key="BooleanReverseToVisibilityCollapsedConverter" /> | ||
| <converters:BooleanReverseConverter x:Key="BooleanReverseConverter" /> |
There was a problem hiding this comment.
BooleanReverseConverter is declared in resources but not referenced anywhere in this XAML. Consider removing it to avoid unused resources (or reference it if it’s intended to be used).
| <converters:BooleanReverseConverter x:Key="BooleanReverseConverter" /> |
| [JsonPropertyName("Settings_FolderLocation")] | ||
| public string? Settings_FolderLocation { get; set; } | ||
|
|
||
| [JsonPropertyName("Profiles_FolderLocation")] | ||
| public string? Profiles_FolderLocation { get; set; } |
There was a problem hiding this comment.
Renaming the JSON key to Settings_FolderLocation is a breaking change for existing config.json deployments that still use SettingsFolderLocation (it won’t deserialize into this property). If backward compatibility is desired, consider keeping an alias property for the old key (marked [Obsolete]) and mapping it to the new property during/after deserialization.
| /// <summary> | ||
| /// Location of the folder where the local settings file is stored. | ||
| /// This can be changed by the user to move the settings file to a different location. | ||
| /// </summary> | ||
| public string SettingsFolderLocation | ||
| public string Settings_FolderLocation | ||
| { | ||
| get => _settingsFolderLocation; | ||
| get => _settings_FolderLocation; | ||
| set | ||
| { | ||
| if (_settingsFolderLocation == value) | ||
| if (_settings_FolderLocation == value) | ||
| return; | ||
|
|
||
| _settingsFolderLocation = value; | ||
| _settings_FolderLocation = value; | ||
| OnPropertyChanged(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Renaming this property to Settings_FolderLocation changes the serialized local settings JSON field name as well, so existing users’ local settings files containing SettingsFolderLocation won’t be picked up and their custom location will silently reset. Consider adding a backward-compatible deserialization path (e.g., an [JsonPropertyName("SettingsFolderLocation")] legacy property that populates Settings_FolderLocation, or a migration step in LocalSettingsManager.Load()).
| Currently supported policies include (more will be added in future releases): | ||
| - Check for updates at startup (see [Update > Check for updates at startup](../settings/update.md#check-for-updates-at-startup) documentation for more information) | ||
| - Settings folder location (see [Settings > Location](../settings/settings.md#location) documentation for more information) | ||
| - Profiles folder location (see [Profiles > Location](../settings/profiles.md#location) documentation for more information) | ||
|
|
There was a problem hiding this comment.
This changelog entry adds the new profiles folder policy, but it doesn’t mention that the policy property names were renamed (e.g., SettingsFolderLocation → Settings_FolderLocation). Without an explicit note, existing admin config.json files may silently stop applying. Consider adding a brief breaking-change/migration note here (and/or linking the PR).
| public string Location | ||
| { | ||
| get => _location; | ||
| set | ||
| { | ||
| if (value == _location) | ||
| return; | ||
|
|
||
| if (!_isLoading) | ||
| IsLocationChanged = !string.Equals(value, ProfileManager.GetProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase); | ||
|
|
||
| _location = value; | ||
| OnPropertyChanged(); | ||
| } | ||
| } |
There was a problem hiding this comment.
IsDefaultLocation is only set once in LoadSettings() and never updated when Location changes. This can leave the “Restore default location” button stuck hidden/shown after the user edits/browses a new path. Consider recalculating IsDefaultLocation (and possibly IsLocationChanged) whenever Location is updated (outside _isLoading).
| /// <summary> | ||
| /// Gets the command that initiates the action to change the location. | ||
| /// </summary> | ||
| public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false)); |
There was a problem hiding this comment.
ChangeLocationCommand invokes ChangeLocationAction().ConfigureAwait(false) but never awaits it. With the current RelayCommand (sync Action<object>), this becomes fire-and-forget and any exceptions in the async flow can go unobserved. Consider using an async lambda (async _ => await ...) or an AsyncRelayCommand-style implementation so the task is awaited/handled.
| public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false)); | |
| public ICommand ChangeLocationCommand => new RelayCommand(async _ => await ChangeLocationAction().ConfigureAwait(false)); |
| /// </summary> | ||
| public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false)); | ||
|
|
There was a problem hiding this comment.
RestoreDefaultLocationCommand invokes RestoreDefaultLocationActionAsync().ConfigureAwait(false) but never awaits it. This is fire-and-forget with potential unobserved exceptions (and ConfigureAwait(false) has no effect unless awaited). Consider wiring the command to an awaited async lambda or using an async command abstraction.
|
|
||
| /// <summary> | ||
| /// Private field for the <see cref="SettingsFolderLocation" /> property." | ||
| /// Private field for the <see cref="Settings_FolderLocation" /> property." |
There was a problem hiding this comment.
There’s an extra trailing quote in the XML doc comment (property."), which looks like a typo.
| /// Private field for the <see cref="Settings_FolderLocation" /> property." | |
| /// Private field for the <see cref="Settings_FolderLocation" /> property. |
Changes proposed in this pull request
Implements the same folder location management for Profiles that PR #3324 added for Settings: user-selectable location via UI, admin-enforced location via system-wide policy, with validation, restart-on-change, and graceful fallback.
Also renames policy/local-settings properties to follow the
Section_SettingNameconvention consistently.Profile location management (mirrors Settings implementation):
ProfileManager.GetProfilesFolderLocation()now resolves with priority: Policy → Custom user path → Default/PortableGetDefaultProfilesFolderLocation(),GetPortableProfilesFolderLocation(),ValidateProfilesFolderPath()with full error handlingSettingsInfo.Profiles_CustomProfilesFolderLocationstores user-selected path (in main settings, not LocalSettingsInfo — settings file is already loaded when profile location is needed)PolicyInfo.Profiles_FolderLocationfor admin-enforced path viaconfig.jsonUI (SettingsProfilesView):
Property renames:
SettingsFolderLocation→Settings_FolderLocation(PolicyInfo, LocalSettingsInfo, all references)ProfilesFolderLocation→Profiles_FolderLocation(PolicyInfo, all references)config.jsonexample:{ "Update_CheckForUpdatesAtStartup": false, "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings", "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles" }Related issue(s)
Based on PR #3324
Copilot generated summary
Copilot summary
This PR adds profile file location management matching the existing settings folder location feature from PR #3324. The
ProfileManager.GetProfilesFolderLocation()method now implements a three-tier priority system (policy override → custom user path → default/portable fallback) with comprehensive path validation. The Settings > Profiles UI is updated with an editable location textbox, browse/drag-drop support, change/restore buttons, and admin policy indicators. Policy properties are renamed fromSettingsFolderLocation/ProfilesFolderLocationtoSettings_FolderLocation/Profiles_FolderLocationfor naming consistency. Documentation updated across profiles.md, settings.md, system-wide-policies.md, and the changelog.To-Do
Contributing
By submitting this pull request, I confirm the following:
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.