Add SettingsFolderLocation policy for centralized settings path control#3324
Conversation
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
|
…ndicator Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
All three items addressed in commit a58b7e0:
The UI now shows the admin indicator below the Location field when The Location field will display the policy-controlled path with the shield icon and accent-colored message when managed by policy. |
Removed reference to the 'Available Policies' section.
…' of https://github.com/BornToBeRoot/NETworkManager into copilot/add-policy-to-override-settings-folder-location
There was a problem hiding this comment.
Pull request overview
Adds a new SettingsFolderLocation system-wide policy (plus a per-user local override) so enterprise deployments can centrally control where NETworkManager stores its settings, and updates the Settings UI + documentation to reflect policy-managed behavior.
Changes:
- Introduce
PolicyInfo.SettingsFolderLocationand updateSettingsManager.GetSettingsFolderLocation()to honor policy/user overrides with validation + fallback. - Add per-user
LocalSettingsManagerstorage (in LocalAppData) to persist a custom settings folder location, and enhance Settings UI (browse, drag/drop, admin shield indicator). - Update docs/changelog and add new path validators/regex to support environment variables and UNC paths.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| Website/docs/system-wide-policies.md | Adds SettingsFolderLocation to policy JSON example. |
| Website/docs/settings/update.md | Wraps System-Wide Policy info in collapsible details block. |
| Website/docs/settings/settings.md | Documents Settings folder location policy + examples. |
| Website/docs/changelog/next-release.md | Updates next-release notes to include supported policies list. |
| Source/NETworkManager/Views/SettingsSettingsView.xaml.cs | Adds drag/drop handlers for location textbox. |
| Source/NETworkManager/Views/SettingsSettingsView.xaml | Reworks Location UI (browse, validation, policy/portable indicators). |
| Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs | Adds location management flags and commands (browse/change/restore). |
| Source/NETworkManager/NETworkManager.csproj | Updates package versions and removes commented publish config. |
| Source/NETworkManager/App.xaml.cs | Loads LocalSettingsManager at startup; saves local settings on exit. |
| Source/NETworkManager.Validators/DirectoryPathWithEnvironmentVariablesValidator.cs | Updates folder-path validation to new regex + message. |
| Source/NETworkManager.Utilities/RegexHelper.cs | Adds directory-path-with-env-vars and UNC regex helpers. |
| Source/NETworkManager.Settings/config.json.example | Adds SettingsFolderLocation to example config. |
| Source/NETworkManager.Settings/SettingsManager.cs | Implements settings-folder precedence (policy → local override → default/portable) + validation. |
| Source/NETworkManager.Settings/SettingsInfo.cs | Adds/updates class-level documentation. |
| Source/NETworkManager.Settings/PolicyManager.cs | Logs SettingsFolderLocation policy value on load. |
| Source/NETworkManager.Settings/PolicyInfo.cs | Adds nullable SettingsFolderLocation policy property. |
| Source/NETworkManager.Settings/LocalSettingsManager.cs | New: local settings loader/saver for per-user settings-path override. |
| Source/NETworkManager.Settings/LocalSettingsInfo.cs | New: local settings model with change tracking. |
| Source/NETworkManager.Profiles/ProfileManager.cs | Improves profile-load logging messages. |
| Source/NETworkManager.Models/NETworkManager.Models.csproj | Updates package versions. |
| Source/NETworkManager.Localization/Resources/Strings.resx | Adds new localized strings for location change/restore + folder-path validation. |
| Source/NETworkManager.Localization/Resources/Strings.Designer.cs | Regenerates strongly-typed string accessors for new resources. |
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.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 8 comments.
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.
| [JsonIgnore] public bool SettingsChanged { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Private field for the <see cref="SettingsFolderLocation" /> property." |
There was a problem hiding this comment.
The XML documentation comment has an extra quotation mark at the end. The closing tag should be /// </summary> not /// </summary>".
| /// Private field for the <see cref="SettingsFolderLocation" /> property." | |
| /// Private field for the <see cref="SettingsFolderLocation" /> property. |
| /// Class contains local settings that are stored outside the main settings file. | ||
| /// These settings control where the main settings file is located. | ||
| /// </summary> | ||
| public class LocalSettingsInfo |
There was a problem hiding this comment.
The LocalSettingsInfo class declares a PropertyChanged event and OnPropertyChanged method consistent with INotifyPropertyChanged pattern, but does not explicitly implement the INotifyPropertyChanged interface. This is inconsistent with the similar SettingsInfo class pattern. Consider adding : INotifyPropertyChanged to the class declaration for clarity and consistency.
| public class LocalSettingsInfo | |
| public class LocalSettingsInfo : INotifyPropertyChanged |
| /// like "C:\Temp", "C:\My Settings", "%AppData%\settings". | ||
| /// </summary> | ||
| /// <returns>A <see cref="Regex"/> instance that matches valid local directory paths.</returns> | ||
| [GeneratedRegex($@"^(?:%[^%]+%|[a-zA-Z]\:)(\\[a-zA-Z0-9_\-\s\.]+)+\\?$")] |
There was a problem hiding this comment.
The regex pattern [a-zA-Z0-9_\-\s\.]+ for directory path validation may be too restrictive for Windows paths. Common valid characters like parentheses (), brackets [], braces {}, tilde ~, exclamation mark !, at sign @, hash #, percent %, caret ^, ampersand &, equals =, plus +, semicolon ;, single quote ', and comma , are not included. This could prevent users from configuring valid Windows paths that contain these characters. Consider expanding the character set to match valid Windows path characters, similar to how FullNameRegex on line 152 includes a broader character set.
| /// The share name may end with $ for hidden shares. | ||
| /// </summary> | ||
| /// <returns>A <see cref="Regex"/> instance that matches valid UNC paths.</returns> | ||
| [GeneratedRegex($@"^\\\\[a-zA-Z0-9_\-\.]+(\\[a-zA-Z0-9_\-\s\.]+\$?)(\\[a-zA-Z0-9_\-\s\.]+)*\\?$")] |
There was a problem hiding this comment.
The UNC path regex [a-zA-Z0-9_\-\s\.]+ has the same limitation as the DirectoryPathWithEnvironmentVariablesRegex. Valid Windows path characters like parentheses, brackets, braces, tilde, and special characters are not included. This could prevent configuration of valid UNC paths. Consider expanding the character set to support all valid Windows path characters.
| /// <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.
The ConfigureAwait(false) call on line 242 is incorrectly placed. When used in a lambda for RelayCommand, the ConfigureAwait method is called on the Task returned by ChangeLocationAction(), but the result is discarded. The fire-and-forget pattern here can lead to unobserved task exceptions. The correct approach is to either make the lambda async and await the task, or handle the task properly. Similar pattern is used in existing codebase commands. Consider using new RelayCommand(async _ => await ChangeLocationAction()) or removing ConfigureAwait entirely since UI context is needed for dialog operations.
| public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false)); | |
| public ICommand ChangeLocationCommand => new RelayCommand(async _ => await ChangeLocationAction()); |
| /// <summary> | ||
| /// Gets the command that restores the default location settings asynchronously. | ||
| /// </summary> | ||
| public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false)); |
There was a problem hiding this comment.
The same ConfigureAwait(false) issue exists here. The result of ConfigureAwait is not being awaited in the lambda, making it a fire-and-forget pattern that can lead to unobserved task exceptions. Consider using new RelayCommand(async _ => await RestoreDefaultLocationActionAsync()) or removing ConfigureAwait since UI context is needed for dialog operations.
| public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false)); | |
| public ICommand RestoreDefaultLocationCommand => new RelayCommand(async _ => await RestoreDefaultLocationActionAsync()); |

Changes proposed in this pull request
SettingsFolderLocationnullable string property toPolicyInfofor path overrideSettingsManager.GetSettingsFolderLocation()to check policy before falling back to portable/non-portable defaultsLocalSettingsManager.Load()to handle null deserialization,IOException,UnauthorizedAccessException, andJsonExceptionApp.xaml.csforLocalSettingsManager.Load()to prevent startup crashesValidateSettingsFolderPath()exception handling to catchPathTooLongExceptionandIOExceptionsettings.mdwith System-Wide Policy info box matching existing pattern fromupdate.mdsystem-wide-policies.mdto avoid duplication (policies documented in respective setting pages)IsLocationManagedByPolicyproperty and admin shield icon) in Settings view to show when location is managed by policyImplementation:
Exception Handling:
The implementation includes robust error handling to prevent startup crashes:
LocalSettingsManager.Load()validates deserialized objects and catches JSON/IO exceptionsApp.xaml.cswraps local settings load with try/catch forIOException,UnauthorizedAccessException, andJsonExceptionPathTooLongExceptionandIOExceptionconfig.json example:
{ "Update_CheckForUpdatesAtStartup": false, "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" }UI Enhancement:
The Settings view now displays a shield icon with "This setting is managed by your administrator" message when the location is controlled by policy, matching the existing pattern in the Update settings view.
Related issue(s)
Based on PR #3313 - System-wide policy infrastructure
Ref #3208 #2787
Copilot generated summary
Copilot summary
This PR adds
SettingsFolderLocationpolicy to enable administrators to centrally control settings storage location across all users. The implementation validates policy-provided paths (absolute, valid characters, directory not file) with comprehensive exception handling (PathTooLongException, IOException, JsonException, UnauthorizedAccessException) and falls back to defaults on validation failure. Documentation restructured to follow existing pattern with System-Wide Policy info boxes in setting pages. UI enhanced with admin indicator (shield icon + message) when location is managed by policy. Robust error handling ensures the application starts gracefully even when local settings files are corrupted, inaccessible, or when policy/custom paths are malformed.To-Do
Contributing
By submitting this pull request, I confirm the following:
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.