diff --git a/Source/NETworkManager.Settings/PolicyInfo.cs b/Source/NETworkManager.Settings/PolicyInfo.cs index 7b9b64a6a0..2f17ee8abf 100644 --- a/Source/NETworkManager.Settings/PolicyInfo.cs +++ b/Source/NETworkManager.Settings/PolicyInfo.cs @@ -10,4 +10,7 @@ public class PolicyInfo { [JsonPropertyName("Update_CheckForUpdatesAtStartup")] public bool? Update_CheckForUpdatesAtStartup { get; set; } + + [JsonPropertyName("SettingsFolderLocation")] + public string? SettingsFolderLocation { get; set; } } diff --git a/Source/NETworkManager.Settings/PolicyManager.cs b/Source/NETworkManager.Settings/PolicyManager.cs index 7dd762c8de..fc946620bc 100644 --- a/Source/NETworkManager.Settings/PolicyManager.cs +++ b/Source/NETworkManager.Settings/PolicyManager.cs @@ -83,6 +83,7 @@ public static void Load() // Log enabled settings Log.Info($"System-wide policy - Update_CheckForUpdatesAtStartup: {Current.Update_CheckForUpdatesAtStartup?.ToString() ?? "Not set"}"); + Log.Info($"System-wide policy - SettingsFolderLocation: {Current.SettingsFolderLocation ?? "Not set"}"); } } catch (Exception ex) diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs index 29a140218e..76961df19b 100644 --- a/Source/NETworkManager.Settings/SettingsManager.cs +++ b/Source/NETworkManager.Settings/SettingsManager.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; +using System.Security; using System.Text.Json; using System.Text.Json.Serialization; using System.Xml.Serialization; @@ -77,6 +78,50 @@ public static class SettingsManager /// Path to the settings folder. public static string GetSettingsFolderLocation() { + // Policy override takes precedence + if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation)) + { + var policyPath = PolicyManager.Current.SettingsFolderLocation; + + // Validate that the policy-provided path is rooted (absolute) + if (!Path.IsPathRooted(policyPath)) + { + Log.Error($"Policy-provided SettingsFolderLocation is not an absolute path: {policyPath}. Falling back to default location."); + } + else + { + // Validate that the path doesn't contain invalid characters + try + { + // This will throw ArgumentException, NotSupportedException, or SecurityException if the path is invalid + var fullPath = Path.GetFullPath(policyPath); + + // Check if the path is a directory (not a file) + if (File.Exists(fullPath)) + { + Log.Error($"Policy-provided SettingsFolderLocation is a file, not a directory: {policyPath}. Falling back to default location."); + } + else + { + return fullPath; + } + } + catch (ArgumentException ex) + { + Log.Error($"Policy-provided SettingsFolderLocation contains invalid characters: {policyPath}. Falling back to default location.", ex); + } + catch (NotSupportedException ex) + { + Log.Error($"Policy-provided SettingsFolderLocation format is not supported: {policyPath}. Falling back to default location.", ex); + } + catch (SecurityException ex) + { + Log.Error($"Insufficient permissions to access policy-provided SettingsFolderLocation: {policyPath}. Falling back to default location.", ex); + } + } + } + + // Fall back to existing logic return ConfigurationManager.Current.IsPortable ? Path.Combine(AssemblyManager.Current.Location, SettingsFolderName) : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example index 8ca2bf05b3..0a47ffa700 100644 --- a/Source/NETworkManager.Settings/config.json.example +++ b/Source/NETworkManager.Settings/config.json.example @@ -1,3 +1,4 @@ { - "Update_CheckForUpdatesAtStartup": false + "Update_CheckForUpdatesAtStartup": false, + "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" } \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs index 330f98e21e..aa41762eb5 100644 --- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs +++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs @@ -31,6 +31,11 @@ public string Location } } + /// + /// Gets whether the settings folder location is managed by system-wide policy. + /// + public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation); + private bool _isDailyBackupEnabled; public bool IsDailyBackupEnabled diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml b/Source/NETworkManager/Views/SettingsSettingsView.xaml index b51f7fe9e6..8d20d869d5 100644 --- a/Source/NETworkManager/Views/SettingsSettingsView.xaml +++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml @@ -7,30 +7,56 @@ xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization" + xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters" mc:Ignorable="d" Loaded="UserControl_Loaded" d:DataContext="{d:DesignInstance viewModels:SettingsSettingsViewModel}"> + + + - + + + + diff --git a/Website/docs/settings/settings.md b/Website/docs/settings/settings.md index 32342b3d6e..c08cf47f3d 100644 --- a/Website/docs/settings/settings.md +++ b/Website/docs/settings/settings.md @@ -17,6 +17,26 @@ Folder where the application settings are stored. | Setup / Archiv | `%UserProfile%\Documents\NETworkManager\Settings` | | Portable | `\Settings` | +:::info System-Wide Policy + +This setting can be controlled by administrators using a system-wide policy. See [System-Wide Policies](../system-wide-policies.md) for more information. + +**Policy Property:** `SettingsFolderLocation` + +**Values:** +- Absolute path (e.g., `C:\\CustomPath\\NETworkManager\\Settings`) - Force a custom settings folder location for all users +- Omit the property - Allow the default location logic to apply (portable vs. non-portable) + +**Example:** + +```json +{ + "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" +} +``` + +::: + :::note **Recommendation** diff --git a/Website/docs/system-wide-policies.md b/Website/docs/system-wide-policies.md index d8ef644453..d6aa0a5c9d 100644 --- a/Website/docs/system-wide-policies.md +++ b/Website/docs/system-wide-policies.md @@ -36,7 +36,8 @@ The `config.json` file uses a simple JSON structure to define policy values. An ```json { - "Update_CheckForUpdatesAtStartup": false + "Update_CheckForUpdatesAtStartup": false, + "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings" } ```