Skip to content

Add SettingsFolderLocation policy for centralized settings path control#3324

Merged
mergify[bot] merged 27 commits intomainfrom
copilot/add-policy-to-override-settings-folder-location
Feb 18, 2026
Merged

Add SettingsFolderLocation policy for centralized settings path control#3324
mergify[bot] merged 27 commits intomainfrom
copilot/add-policy-to-override-settings-folder-location

Conversation

Copy link
Contributor

Copilot AI commented Feb 9, 2026

Changes proposed in this pull request

  • Extends policy system from PR Add system-wide policy configuration for centralized update control #3313 to support custom settings folder location
  • Adds SettingsFolderLocation nullable string property to PolicyInfo for path override
  • Modifies SettingsManager.GetSettingsFolderLocation() to check policy before falling back to portable/non-portable defaults
  • Validates policy paths are absolute, contain no invalid characters, and are directories (not files), with graceful fallback on failure
  • Adds comprehensive exception handling for LocalSettingsManager.Load() to handle null deserialization, IOException, UnauthorizedAccessException, and JsonException
  • Adds exception handling in App.xaml.cs for LocalSettingsManager.Load() to prevent startup crashes
  • Extends ValidateSettingsFolderPath() exception handling to catch PathTooLongException and IOException
  • Updates documentation in settings.md with System-Wide Policy info box matching existing pattern from update.md
  • Removes "Available Policies" section from system-wide-policies.md to avoid duplication (policies documented in respective setting pages)
  • Adds UI indicator (IsLocationManagedByPolicy property and admin shield icon) in Settings view to show when location is managed by policy

Implementation:

public static string GetSettingsFolderLocation()
{
    // Policy override takes precedence with validation
    if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation))
    {
        var policyPath = PolicyManager.Current.SettingsFolderLocation;
        if (!Path.IsPathRooted(policyPath)) { /* log error, fallback */ }
        try { 
            var fullPath = Path.GetFullPath(policyPath);
            if (File.Exists(fullPath)) { /* log error (is file not directory), fallback */ }
            else { return fullPath; }
        }
        catch (ArgumentException | NotSupportedException | SecurityException 
               | PathTooLongException | IOException) { /* log error, fallback */ }
    }
    
    // Fall back to existing logic
    return ConfigurationManager.Current.IsPortable ? /* ... */;
}

Exception Handling:

The implementation includes robust error handling to prevent startup crashes:

  • LocalSettingsManager.Load() validates deserialized objects and catches JSON/IO exceptions
  • App.xaml.cs wraps local settings load with try/catch for IOException, UnauthorizedAccessException, and JsonException
  • Path validation catches all possible exceptions including PathTooLongException and IOException

config.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 SettingsFolderLocation policy 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.

Copilot AI and others added 6 commits February 9, 2026 20:11
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>
Copilot AI changed the title [WIP] Add policy to override SettingsFolder location in SettingsManager Add SettingsFolderLocation policy for centralized settings path control Feb 9, 2026
Copilot AI requested a review from BornToBeRoot February 9, 2026 20:19
@BornToBeRoot
Copy link
Owner

@copilot

  1. In GetSettingsFolderLocation() you should check it the path is a directory
  2. Add a note to the settings.md similar to what exists in the update.md and remove the "Available Policies" in the system-wide-policies.md
  3. Add also the is managed by admin icon/message to the property in the settings view similar to the update view

…ndicator

Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 9, 2026

@copilot

  1. In GetSettingsFolderLocation() you should check it the path is a directory
    ...

All three items addressed in commit a58b7e0:

  1. Directory validation: Added File.Exists() check in GetSettingsFolderLocation() to ensure the policy path is not a file
  2. Documentation updated: Added System-Wide Policy info box to settings.md matching the format in update.md, and removed the "Available Policies" section from system-wide-policies.md
  3. Admin indicator added: Added IsLocationManagedByPolicy property to SettingsSettingsViewModel and added the shield icon with "This setting is managed by your administrator" message to SettingsSettingsView.xaml

The UI now shows the admin indicator below the Location field when SettingsFolderLocation is set in config.json, matching the behavior of the Update settings:

image

The Location field will display the policy-controlled path with the shield icon and accent-colored message when managed by policy.

@BornToBeRoot BornToBeRoot marked this pull request as ready for review February 18, 2026 00:19
@BornToBeRoot BornToBeRoot requested a review from Copilot February 18, 2026 00:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.SettingsFolderLocation and update SettingsManager.GetSettingsFolderLocation() to honor policy/user overrides with validation + fallback.
  • Add per-user LocalSettingsManager storage (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.

BornToBeRoot and others added 3 commits February 18, 2026 01:34
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>
@BornToBeRoot BornToBeRoot added this to the next-release milestone Feb 18, 2026
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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."
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML documentation comment has an extra quotation mark at the end. The closing tag should be /// </summary> not /// </summary>".

Suggested change
/// Private field for the <see cref="SettingsFolderLocation" /> property."
/// Private field for the <see cref="SettingsFolderLocation" /> property.

Copilot uses AI. Check for mistakes.
/// 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
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
public class LocalSettingsInfo
public class LocalSettingsInfo : INotifyPropertyChanged

Copilot uses AI. Check for mistakes.
/// 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\.]+)+\\?$")]
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
/// 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\.]+)*\\?$")]
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
/// <summary>
/// Gets the command that initiates the action to change the location.
/// </summary>
public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false));
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false));
public ICommand ChangeLocationCommand => new RelayCommand(async _ => await ChangeLocationAction());

Copilot uses AI. Check for mistakes.
/// <summary>
/// Gets the command that restores the default location settings asynchronously.
/// </summary>
public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false));
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false));
public ICommand RestoreDefaultLocationCommand => new RelayCommand(async _ => await RestoreDefaultLocationActionAsync());

Copilot uses AI. Check for mistakes.
@mergify mergify bot merged commit ea62b94 into main Feb 18, 2026
10 checks passed
@mergify mergify bot deleted the copilot/add-policy-to-override-settings-folder-location branch February 18, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments