diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 40ef350d30..f7699adf75 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -1464,17 +1464,6 @@ public static string ChangeLocationQuestion {
}
}
- ///
- /// Sucht eine lokalisierte Zeichenfolge, die The location is changed and the application is restarted afterwards.
- ///
- ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt.
- ///
- public static string ChangeLocationSettingsMessage {
- get {
- return ResourceManager.GetString("ChangeLocationSettingsMessage", resourceCulture);
- }
- }
-
///
/// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt.
///
@@ -1502,6 +1491,28 @@ public static string ChangeMasterPasswordDots {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die The profiles location is changed and the application is restarted afterwards.
+ ///
+ ///You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt.
+ ///
+ public static string ChangeProfilesLocationMessage {
+ get {
+ return ResourceManager.GetString("ChangeProfilesLocationMessage", resourceCulture);
+ }
+ }
+
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die The settings location is changed and the application is restarted afterwards.
+ ///
+ ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt.
+ ///
+ public static string ChangeSettingsLocationMessage {
+ get {
+ return ResourceManager.GetString("ChangeSettingsLocationMessage", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Channel ähnelt.
///
@@ -8892,13 +8903,13 @@ public static string RestoreDefaultLocationQuestion {
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die The default path is restored and the application is restarted afterwards.
+ /// Sucht eine lokalisierte Zeichenfolge, die The default profiles location is restored and the application is restarted afterwards.
///
- ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt.
+ ///You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt.
///
- public static string RestoreDefaultLocationSettingsMessage {
+ public static string RestoreDefaultProfilesLocationMessage {
get {
- return ResourceManager.GetString("RestoreDefaultLocationSettingsMessage", resourceCulture);
+ return ResourceManager.GetString("RestoreDefaultProfilesLocationMessage", resourceCulture);
}
}
@@ -8911,6 +8922,17 @@ public static string RestoreDefaults {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die The default settings location is restored and the application is restarted afterwards.
+ ///
+ ///You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten. ähnelt.
+ ///
+ public static string RestoreDefaultSettingsLocationMessage {
+ get {
+ return ResourceManager.GetString("RestoreDefaultSettingsLocationMessage", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Result ähnelt.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index ad2eb07c93..7c2471b85e 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3972,20 +3972,30 @@ If you click Cancel, the profile file will remain unencrypted.
Restore default location?
-
- The default path is restored and the application is restarted afterwards.
+
+ The default settings location is restored and the application is restarted afterwards.
You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten.Change location?
-
- The location is changed and the application is restarted afterwards.
+
+ The settings location is changed and the application is restarted afterwards.
You can copy the “settings.json” file from "{0}" to "{1}" to migrate your previous settings, if necessary. The application must be closed for this to prevent the settings from being overwritten.Enter a valid folder path!
+
+ The profiles location is changed and the application is restarted afterwards.
+
+You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten.
+
+
+ The default profiles location is restored and the application is restarted afterwards.
+
+You can copy your profile files from “{0}” to “{1}” to migrate your existing profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten.
+
\ No newline at end of file
diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs
index bbb62fa382..c32ae88977 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -197,14 +197,65 @@ private static void ProfilesUpdated(bool profilesChanged = true)
///
/// Method to get the path of the profiles folder.
+ /// Priority:
+ /// 1. Policy override (for IT administrators)
+ /// 2. Custom user-configured path (not available in portable mode)
+ /// 3. Portable (same directory as the application) or default location (Documents folder)
///
/// Path to the profiles folder.
public static string GetProfilesFolderLocation()
{
- return ConfigurationManager.Current.IsPortable
- ? Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName)
- : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
- AssemblyManager.Current.Name, ProfilesFolderName);
+ // 1. Policy override takes precedence (for IT administrators)
+ if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation))
+ {
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
+ PolicyManager.Current.Profiles_FolderLocation,
+ "Policy-provided",
+ nameof(PolicyInfo.Profiles_FolderLocation),
+ "next priority");
+
+ if (validatedPath != null)
+ return validatedPath;
+ }
+
+ // 2. Custom user-configured path (not available in portable mode)
+ if (!ConfigurationManager.Current.IsPortable &&
+ !string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_FolderLocation))
+ {
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
+ SettingsManager.Current.Profiles_FolderLocation,
+ "Custom",
+ nameof(SettingsInfo.Profiles_FolderLocation),
+ "default location");
+
+ if (validatedPath != null)
+ return validatedPath;
+ }
+
+ // 3. Fall back to portable or default location
+ if (ConfigurationManager.Current.IsPortable)
+ return GetPortableProfilesFolderLocation();
+ else
+ return GetDefaultProfilesFolderLocation();
+ }
+
+ ///
+ /// Method to get the default profiles folder location in the user's Documents directory.
+ ///
+ /// Path to the default profiles folder location.
+ public static string GetDefaultProfilesFolderLocation()
+ {
+ return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
+ AssemblyManager.Current.Name, ProfilesFolderName);
+ }
+
+ ///
+ /// Method to get the portable profiles folder location (in the same directory as the application).
+ ///
+ /// Path to the portable profiles folder location.
+ public static string GetPortableProfilesFolderLocation()
+ {
+ return Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName);
}
///
diff --git a/Source/NETworkManager.Settings/LocalSettingsInfo.cs b/Source/NETworkManager.Settings/LocalSettingsInfo.cs
index 6c7d1a692f..79c02a1937 100644
--- a/Source/NETworkManager.Settings/LocalSettingsInfo.cs
+++ b/Source/NETworkManager.Settings/LocalSettingsInfo.cs
@@ -34,23 +34,23 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
[JsonIgnore] public bool SettingsChanged { get; set; }
///
- /// Private field for the property."
+ /// Private field for the property.
///
- private string _settingsFolderLocation;
+ private string _settings_FolderLocation;
///
/// 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.
///
- 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();
}
}
diff --git a/Source/NETworkManager.Settings/PolicyInfo.cs b/Source/NETworkManager.Settings/PolicyInfo.cs
index 2f17ee8abf..811360e765 100644
--- a/Source/NETworkManager.Settings/PolicyInfo.cs
+++ b/Source/NETworkManager.Settings/PolicyInfo.cs
@@ -11,6 +11,9 @@ public class PolicyInfo
[JsonPropertyName("Update_CheckForUpdatesAtStartup")]
public bool? Update_CheckForUpdatesAtStartup { get; set; }
- [JsonPropertyName("SettingsFolderLocation")]
- public string? SettingsFolderLocation { get; set; }
+ [JsonPropertyName("Profiles_FolderLocation")]
+ public string? Profiles_FolderLocation { get; set; }
+
+ [JsonPropertyName("Settings_FolderLocation")]
+ public string? Settings_FolderLocation { get; set; }
}
diff --git a/Source/NETworkManager.Settings/PolicyManager.cs b/Source/NETworkManager.Settings/PolicyManager.cs
index ddbdfd3d38..e7708db5fa 100644
--- a/Source/NETworkManager.Settings/PolicyManager.cs
+++ b/Source/NETworkManager.Settings/PolicyManager.cs
@@ -84,7 +84,8 @@ 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"}");
+ Log.Info($"System-wide policy - Profiles_FolderLocation: {Current.Profiles_FolderLocation ?? "Not set"}");
+ Log.Info($"System-wide policy - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "Not set"}");
}
}
catch (Exception ex)
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 5e810c8ec3..01f641b0b4 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -576,7 +576,22 @@ public bool Experimental_EnableExperimentalFeatures
}
}
- // Profiles
+ // Profiles
+ private string _profiles_FolderLocation;
+
+ public string Profiles_FolderLocation
+ {
+ get => _profiles_FolderLocation;
+ set
+ {
+ if (value == _profiles_FolderLocation)
+ return;
+
+ _profiles_FolderLocation = value;
+ OnPropertyChanged();
+ }
+ }
+
private string _profiles_LastSelected;
public string Profiles_LastSelected
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 8a289a632d..286a928728 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -5,7 +5,6 @@
using System;
using System.IO;
using System.Linq;
-using System.Security;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
@@ -74,16 +73,21 @@ public static class SettingsManager
///
/// Method to get the path of the settings folder.
+ /// Priority:
+ /// 1. Policy override (for IT administrators)
+ /// 2. Custom user-configured path (not available in portable mode)
+ /// 3. Portable (same directory as the application) or default location (Documents folder)
///
/// Path to the settings folder.
public static string GetSettingsFolderLocation()
{
// 1. Policy override takes precedence (for IT administrators)
- if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation))
+ if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation))
{
- var validatedPath = ValidateSettingsFolderPath(
- PolicyManager.Current.SettingsFolderLocation,
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
+ PolicyManager.Current.Settings_FolderLocation,
"Policy-provided",
+ nameof(PolicyInfo.Settings_FolderLocation),
"next priority");
if (validatedPath != null)
@@ -92,11 +96,12 @@ public static string GetSettingsFolderLocation()
// 2. Custom user-configured path (not available in portable mode)
if (!ConfigurationManager.Current.IsPortable &&
- !string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.SettingsFolderLocation))
+ !string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.Settings_FolderLocation))
{
- var validatedPath = ValidateSettingsFolderPath(
- LocalSettingsManager.Current.SettingsFolderLocation,
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
+ LocalSettingsManager.Current.Settings_FolderLocation,
"Custom",
+ nameof(LocalSettingsInfo.Settings_FolderLocation),
"default location");
if (validatedPath != null)
@@ -129,67 +134,6 @@ public static string GetPortableSettingsFolderLocation()
return Path.Combine(AssemblyManager.Current.Location, SettingsFolderName);
}
- ///
- /// Validates a settings folder path for correctness and accessibility.
- ///
- /// The path to validate.
- /// Description of the path source for logging (e.g., "Policy-provided", "Custom").
- /// Message describing what happens on validation failure (e.g., "next priority", "default location").
- /// The validated full path if valid; otherwise, null.
- private static string ValidateSettingsFolderPath(string path, string pathSource, string fallbackMessage)
- {
- // Expand environment variables first (e.g. %userprofile%\settings -> C:\Users\...\settings)
- path = Environment.ExpandEnvironmentVariables(path);
-
- // Validate that the path is rooted (absolute)
- if (!Path.IsPathRooted(path))
- {
- Log.Error($"{pathSource} SettingsFolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}.");
- return null;
- }
-
- // Validate that the path doesn't contain invalid characters
- try
- {
- // This will throw ArgumentException, NotSupportedException, SecurityException, PathTooLongException, or IOException if the path is invalid
- var fullPath = Path.GetFullPath(path);
-
- // Check if the path is a directory (not a file)
- if (File.Exists(fullPath))
- {
- Log.Error($"{pathSource} SettingsFolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
- return null;
- }
-
- return Path.TrimEndingDirectorySeparator(fullPath);
- }
- catch (ArgumentException ex)
- {
- Log.Error($"{pathSource} SettingsFolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (NotSupportedException ex)
- {
- Log.Error($"{pathSource} SettingsFolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (SecurityException ex)
- {
- Log.Error($"Insufficient permissions to access {pathSource} SettingsFolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (PathTooLongException ex)
- {
- Log.Error($"{pathSource} SettingsFolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (IOException ex)
- {
- Log.Error($"{pathSource} SettingsFolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- }
-
///
/// Method to get the path of the settings backup folder.
///
diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example
index 0a47ffa700..2bf8307cfb 100644
--- a/Source/NETworkManager.Settings/config.json.example
+++ b/Source/NETworkManager.Settings/config.json.example
@@ -1,4 +1,5 @@
{
"Update_CheckForUpdatesAtStartup": false,
- "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
+ "Profiles_FolderLocation": "\\\\Server\\Shared\\NETworkManager\\Profiles",
+ "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings"
}
\ No newline at end of file
diff --git a/Source/NETworkManager.Utilities/DirectoryHelper.cs b/Source/NETworkManager.Utilities/DirectoryHelper.cs
index 25353b8cc1..c7aeef7efb 100644
--- a/Source/NETworkManager.Utilities/DirectoryHelper.cs
+++ b/Source/NETworkManager.Utilities/DirectoryHelper.cs
@@ -1,5 +1,7 @@
using System;
using System.IO;
+using System.Security;
+using log4net;
namespace NETworkManager.Utilities;
@@ -8,6 +10,11 @@ namespace NETworkManager.Utilities;
///
public static class DirectoryHelper
{
+ ///
+ /// Logger for logging.
+ ///
+ private static readonly ILog Log = LogManager.GetLogger(typeof(DirectoryHelper));
+
///
/// Create a directory with subdirectories and resolve environment variables.
///
@@ -16,4 +23,67 @@ public static void CreateWithEnvironmentVariables(string path)
{
Directory.CreateDirectory(Environment.ExpandEnvironmentVariables(path));
}
+
+ ///
+ /// Validates a folder path for correctness and accessibility.
+ /// Expands environment variables, checks that the path is absolute,
+ /// validates characters, and ensures it does not point to a file.
+ ///
+ /// The path to validate.
+ /// Description of the path source for logging (e.g., "Policy-provided", "Custom").
+ /// Name of the property being validated for logging (e.g., "Settings_FolderLocation").
+ /// Message describing what happens on validation failure (e.g., "next priority", "default location").
+ /// The validated full path if valid; otherwise, null.
+ public static string ValidateFolderPath(string path, string pathSource, string propertyName, string fallbackMessage)
+ {
+ // Expand environment variables first (e.g. %userprofile%\settings -> C:\Users\...\settings)
+ path = Environment.ExpandEnvironmentVariables(path);
+
+ // Validate that the path is rooted (absolute)
+ if (!Path.IsPathRooted(path))
+ {
+ Log.Error($"{pathSource} {propertyName} is not an absolute path: {path}. Falling back to {fallbackMessage}.");
+ return null;
+ }
+
+ // Validate that the path doesn't contain invalid characters
+ try
+ {
+ var fullPath = Path.GetFullPath(path);
+
+ // Check if the path is a directory (not a file)
+ if (File.Exists(fullPath))
+ {
+ Log.Error($"{pathSource} {propertyName} is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
+ return null;
+ }
+
+ return Path.TrimEndingDirectorySeparator(fullPath);
+ }
+ catch (ArgumentException ex)
+ {
+ Log.Error($"{pathSource} {propertyName} contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (NotSupportedException ex)
+ {
+ Log.Error($"{pathSource} {propertyName} format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (SecurityException ex)
+ {
+ Log.Error($"Insufficient permissions to access {pathSource} {propertyName}: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (PathTooLongException ex)
+ {
+ Log.Error($"{pathSource} {propertyName} path is too long: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (IOException ex)
+ {
+ Log.Error($"{pathSource} {propertyName} caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ }
}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index e2c58d980d..0173619963 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -7,6 +7,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
@@ -34,13 +35,69 @@ public string Location
if (value == _location)
return;
+ if (!_isLoading)
+ IsLocationChanged = !string.Equals(value, ProfileManager.GetProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase);
+
_location = value;
OnPropertyChanged();
}
}
+ ///
+ /// Indicates whether the profiles location is managed by a system-wide policy.
+ ///
+ public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation);
+
+ ///
+ /// Private field of property.
+ ///
+ private bool _isLocationChanged;
+
+ ///
+ /// Gets or sets a value indicating whether the location has changed.
+ ///
+ public bool IsLocationChanged
+ {
+ get => _isLocationChanged;
+ set
+ {
+ if (value == _isLocationChanged)
+ return;
+
+ _isLocationChanged = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Private field of property.
+ ///
+ private bool _isDefaultLocation;
+
+ ///
+ /// Indicates whether the current location is the default profiles folder location.
+ ///
+ public bool IsDefaultLocation
+ {
+ get => _isDefaultLocation;
+ set
+ {
+ if (value == _isDefaultLocation)
+ return;
+
+ _isDefaultLocation = value;
+ OnPropertyChanged();
+ }
+ }
+
+ ///
+ /// Private field of property.
+ ///
private readonly ICollectionView _profileFiles;
+ ///
+ /// Gets the collection view of profile files.
+ ///
public ICollectionView ProfileFiles
{
get => _profileFiles;
@@ -54,8 +111,15 @@ private init
}
}
+ ///
+ /// Private field of property.
+ ///
+
private ProfileFileInfo _selectedProfileFile;
+ ///
+ /// Gets or sets the currently selected profile file information.
+ ///
public ProfileFileInfo SelectedProfileFile
{
get => _selectedProfileFile;
@@ -69,8 +133,15 @@ public ProfileFileInfo SelectedProfileFile
}
}
+ ///
+ /// Private field of property.
+ ///
private bool _isDailyBackupEnabled;
+
+ ///
+ /// Gets or sets a value indicating whether daily backups are enabled.
+ ///
public bool IsDailyBackupEnabled
{
get => _isDailyBackupEnabled;
@@ -87,8 +158,14 @@ public bool IsDailyBackupEnabled
}
}
+ ///
+ /// Private field of property.
+ ///
private int _maximumNumberOfBackups;
+ ///
+ /// Gets or sets the maximum number of backups to keep.
+ ///
public int MaximumNumberOfBackups
{
get => _maximumNumberOfBackups;
@@ -108,6 +185,9 @@ public int MaximumNumberOfBackups
#region Constructor, LoadSettings
+ ///
+ /// Initializes a new instance of the class and loads the current profile files.
+ ///
public SettingsProfilesViewModel()
{
_isLoading = true;
@@ -123,9 +203,13 @@ public SettingsProfilesViewModel()
_isLoading = false;
}
+ ///
+ /// Load view specific settings.
+ ///
private void LoadSettings()
{
Location = ProfileManager.GetProfilesFolderLocation();
+ IsDefaultLocation = string.Equals(Location, ProfileManager.GetDefaultProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase);
IsDailyBackupEnabled = SettingsManager.Current.Profiles_IsDailyBackupEnabled;
MaximumNumberOfBackups = SettingsManager.Current.Profiles_MaximumNumberOfBackups;
}
@@ -133,12 +217,99 @@ private void LoadSettings()
#endregion
#region ICommands & Actions
+ ///
+ /// Gets the command that opens the location folder selection dialog.
+ ///
+ public ICommand BrowseLocationFolderCommand => new RelayCommand(p => BrowseLocationFolderAction());
+
+ ///
+ /// Opens a dialog that allows the user to select a folder location and updates the Location property with the
+ /// selected path if the user confirms the selection.
+ ///
+ private void BrowseLocationFolderAction()
+ {
+ using var dialog = new System.Windows.Forms.FolderBrowserDialog();
+
+ if (Directory.Exists(Location))
+ dialog.SelectedPath = Location;
+ var dialogResult = dialog.ShowDialog();
+
+ if (dialogResult == System.Windows.Forms.DialogResult.OK)
+ Location = dialog.SelectedPath;
+ }
+
+
+ ///
+ /// Gets the command that initiates the action to change the location.
+ ///
public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction());
private static void OpenLocationAction()
{
Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation());
+ }
+
+ ///
+ /// Gets the command that initiates the action to change the location.
+ ///
+ public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false));
+
+ ///
+ /// Prompts the user to confirm and then changes the location of the profiles folder.
+ ///
+ /// A task that represents the asynchronous operation.
+ private async Task ChangeLocationAction()
+ {
+ var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
+ Strings.ChangeLocationQuestion,
+ string.Format(Strings.ChangeProfilesLocationMessage, ProfileManager.GetProfilesFolderLocation(), Location),
+ ChildWindowIcon.Question,
+ Strings.Change);
+
+ if (!result)
+ return;
+
+ // Save profiles at the current location before changing it to prevent
+ // unintended saves to the new location (e.g., triggered by background timer or the app close & restart).
+ ProfileManager.Save();
+
+ // Set new location in SettingsInfo
+ SettingsManager.Current.Profiles_FolderLocation = Location;
+
+ // Restart the application
+ (Application.Current.MainWindow as MainWindow)?.RestartApplication();
+ }
+
+ ///
+ /// Gets the command that restores the default location.
+ ///
+ public ICommand RestoreDefaultLocationCommand => new RelayCommand(_ => RestoreDefaultLocationActionAsync().ConfigureAwait(false));
+
+ ///
+ /// Restores the profiles folder location to the default path after obtaining user confirmation.
+ ///
+ /// A task that represents the asynchronous operation.
+ private async Task RestoreDefaultLocationActionAsync()
+ {
+ var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
+ Strings.RestoreDefaultLocationQuestion,
+ string.Format(Strings.RestoreDefaultProfilesLocationMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()),
+ ChildWindowIcon.Question,
+ Strings.Restore);
+
+ if (!result)
+ return;
+
+ // Save profiles at the current location before changing it to prevent
+ // unintended saves to the new location (e.g., triggered by background timer or the app close & restart).
+ ProfileManager.Save();
+
+ // Clear custom location to revert to default
+ SettingsManager.Current.Profiles_FolderLocation = null;
+
+ // Restart the application
+ (Application.Current.MainWindow as MainWindow)?.RestartApplication();
}
public ICommand AddProfileFileCommand => new RelayCommand(async _ => await AddProfileFileAction().ConfigureAwait(false));
@@ -410,4 +581,15 @@ await DialogHelper.ShowMessageAsync(Application.Current.MainWindow,
}
#endregion
+
+ #region Methods
+ ///
+ /// Sets the location path based on the provided drag-and-drop input.
+ ///
+ /// The path to set as the location.
+ public void SetLocationPathFromDragDrop(string path)
+ {
+ Location = path;
+ }
+ #endregion
}
diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
index e14ee3639c..2a1b39439d 100644
--- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
@@ -50,7 +50,7 @@ public string Location
///
/// Indicates whether the settings location is managed by a system-wide policy.
///
- public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation);
+ public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation);
///
/// Private field of property.
@@ -159,8 +159,8 @@ public SettingsSettingsViewModel()
}
///
- /// Loads the application settings from the current settings folder location.
- ///
+ /// Load view specific settings.
+ ///
private void LoadSettings()
{
Location = SettingsManager.GetSettingsFolderLocation();
@@ -172,36 +172,6 @@ private void LoadSettings()
#endregion
#region ICommands & Actions
-
- ///
- /// Gets the command that opens a location when executed.
- ///
- public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction());
-
- ///
- /// Opens the settings folder location in Windows Explorer.
- ///
- private static void OpenLocationAction()
- {
- Process.Start("explorer.exe", SettingsManager.GetSettingsFolderLocation());
- }
-
- ///
- /// Gets the command that resets the application settings to their default values.
- ///
- public ICommand ResetSettingsCommand => new RelayCommand(_ => ResetSettingsAction());
-
- ///
- /// Resets the application settings to their default values.
- ///
- private void ResetSettingsAction()
- {
- ResetSettings().ConfigureAwait(false);
- }
-
- #endregion
-
- #region Methods
///
/// Gets the command that opens the location folder selection dialog.
///
@@ -210,10 +180,7 @@ private void ResetSettingsAction()
///
/// Opens a dialog that allows the user to select a folder location and updates the Location property with the
/// selected path if the user confirms the selection.
- ///
- /// If the Location property is set to a valid directory path, it is pre-selected in the dialog.
- /// This method does not return a value and is intended for use in a user interface context where folder selection
- /// is required.
+ ///
private void BrowseLocationFolderAction()
{
using var dialog = new System.Windows.Forms.FolderBrowserDialog();
@@ -228,12 +195,16 @@ private void BrowseLocationFolderAction()
}
///
- /// Sets the location path based on the provided drag-and-drop input.
- ///
- /// The path to set as the location. This value cannot be null or empty.
- public void SetLocationPathFromDragDrop(string path)
+ /// Gets the command that opens a location when executed.
+ ///
+ public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction());
+
+ ///
+ /// Opens the settings folder location in Windows Explorer.
+ ///
+ private static void OpenLocationAction()
{
- Location = path;
+ Process.Start("explorer.exe", SettingsManager.GetSettingsFolderLocation());
}
///
@@ -242,18 +213,14 @@ public void SetLocationPathFromDragDrop(string path)
public ICommand ChangeLocationCommand => new RelayCommand(_ => ChangeLocationAction().ConfigureAwait(false));
///
- /// Prompts the user to confirm and then changes the location of the application's settings folder.
+ /// Prompts the user to confirm and then changes the location of the settings folder.
///
- /// This method displays a confirmation dialog to the user before changing the settings folder
- /// location. If the user confirms, it saves the current settings, updates the settings folder location, and
- /// restarts the application to apply the changes. No action is taken if the user cancels the confirmation
- /// dialog.
/// A task that represents the asynchronous operation.
private async Task ChangeLocationAction()
{
var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
Strings.ChangeLocationQuestion,
- string.Format(Strings.ChangeLocationSettingsMessage, SettingsManager.GetSettingsFolderLocation(), Location),
+ string.Format(Strings.ChangeSettingsLocationMessage, SettingsManager.GetSettingsFolderLocation(), Location),
ChildWindowIcon.Question,
Strings.Change);
@@ -265,7 +232,7 @@ private async Task ChangeLocationAction()
SettingsManager.Save();
// Set new location
- LocalSettingsManager.Current.SettingsFolderLocation = Location;
+ LocalSettingsManager.Current.Settings_FolderLocation = Location;
LocalSettingsManager.Save();
// Restart the application
@@ -289,7 +256,7 @@ private async Task RestoreDefaultLocationActionAsync()
{
var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
Strings.RestoreDefaultLocationQuestion,
- string.Format(Strings.RestoreDefaultLocationSettingsMessage, SettingsManager.GetSettingsFolderLocation(), SettingsManager.GetDefaultSettingsFolderLocation()),
+ string.Format(Strings.RestoreDefaultSettingsLocationMessage, SettingsManager.GetSettingsFolderLocation(), SettingsManager.GetDefaultSettingsFolderLocation()),
ChildWindowIcon.Question,
Strings.Restore);
@@ -301,7 +268,7 @@ private async Task RestoreDefaultLocationActionAsync()
SettingsManager.Save();
// Clear custom location to revert to default
- LocalSettingsManager.Current.SettingsFolderLocation = null;
+ LocalSettingsManager.Current.Settings_FolderLocation = null;
LocalSettingsManager.Save();
// Restart the application
@@ -309,19 +276,21 @@ private async Task RestoreDefaultLocationActionAsync()
}
///
- /// Resets the application settings to their default values and restarts the application after user confirmation.
+ /// Gets the command that resets the application settings to their default values.
+ ///
+ public ICommand ResetSettingsCommand => new RelayCommand(_ => ResetSettingsAction().ConfigureAwait(false));
+
+ ///
+ /// Resets the application settings to their default values.
///
- /// Displays a confirmation dialog to the user before proceeding. If the user confirms, the
- /// settings are reinitialized to their defaults and the application is restarted. No action is taken if the user
- /// cancels the confirmation dialog.
/// A task that represents the asynchronous operation.
- private async Task ResetSettings()
+ private async Task ResetSettingsAction()
{
var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
- Strings.ResetSettingsQuestion,
- Strings.SettingsAreResetAndApplicationWillBeRestartedMessage,
- ChildWindowIcon.Question,
- Strings.Reset);
+ Strings.ResetSettingsQuestion,
+ Strings.SettingsAreResetAndApplicationWillBeRestartedMessage,
+ ChildWindowIcon.Question,
+ Strings.Reset);
if (!result)
return;
@@ -332,5 +301,17 @@ private async Task ResetSettings()
// Restart the application
(Application.Current.MainWindow as MainWindow)?.RestartApplication();
}
+
+ #endregion
+
+ #region Methods
+ ///
+ /// Sets the location path based on the provided drag-and-drop input.
+ ///
+ /// The path to set as the location.
+ public void SetLocationPathFromDragDrop(string path)
+ {
+ Location = path;
+ }
#endregion
}
diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml
index 0989c4a24d..efa4d9c5d4 100644
--- a/Source/NETworkManager/Views/SettingsProfilesView.xaml
+++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml
@@ -10,36 +10,182 @@
xmlns:viewModels="clr-namespace:NETworkManager.ViewModels"
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
xmlns:profiles="clr-namespace:NETworkManager.Profiles;assembly=NETworkManager.Profiles"
+ xmlns:validators="clr-namespace:NETworkManager.Validators;assembly=NETworkManager.Validators"
+ xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:SettingsProfilesViewModel}"
Loaded="UserControl_Loaded">
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 })
+ _viewModel.SetLocationPathFromDragDrop(files[0]);
+ }
+
+ private void TextBoxLocation_PreviewDragOver(object sender, DragEventArgs e)
+ {
+ e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
+ ? DragDropEffects.Copy
+ : DragDropEffects.None;
+ e.Handled = true;
+ }
}
\ No newline at end of file
diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml b/Source/NETworkManager/Views/SettingsSettingsView.xaml
index d1db5b42be..44246e210f 100644
--- a/Source/NETworkManager/Views/SettingsSettingsView.xaml
+++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml
@@ -18,7 +18,8 @@
-
+
-
-
-
+
+ 0 })
_viewModel.SetLocationPathFromDragDrop(files[0]);
}
private void TextBoxLocation_PreviewDragOver(object sender, DragEventArgs e)
{
- e.Effects = DragDropEffects.Copy;
+ e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
+ ? DragDropEffects.Copy
+ : DragDropEffects.None;
e.Handled = true;
}
-}
\ No newline at end of file
+}
diff --git a/Website/blog/2026-02-19-system-wide-policies-for-enterprise-deployments/index.md b/Website/blog/2026-02-22-system-wide-policies-for-enterprise-deployments/index.md
similarity index 79%
rename from Website/blog/2026-02-19-system-wide-policies-for-enterprise-deployments/index.md
rename to Website/blog/2026-02-22-system-wide-policies-for-enterprise-deployments/index.md
index e01b5831eb..7a6d1e0404 100644
--- a/Website/blog/2026-02-19-system-wide-policies-for-enterprise-deployments/index.md
+++ b/Website/blog/2026-02-22-system-wide-policies-for-enterprise-deployments/index.md
@@ -38,19 +38,27 @@ Property names follow the pattern `Section_SettingName`. You can find the availa
### Example Policy
-For example, the [`Update_CheckForUpdatesAtStartup`](https://borntoberoot.net/NETworkManager/docs/settings/update) policy controls whether the application checks for new program versions on GitHub when the application is launched.
+For example, the [`Update_CheckForUpdatesAtStartup`](https://borntoberoot.net/NETworkManager/docs/settings/update) policy controls whether the application checks for new program versions on GitHub when the application is launched. The [`Profiles_FolderLocation`](https://borntoberoot.net/NETworkManager/docs/settings/profiles) and [`Settings_FolderLocation`](https://borntoberoot.net/NETworkManager/docs/settings/settings) policies allow administrators to redirect profile and settings storage to a centralized location, such as a network share or a custom local path.
**Values:**
-- `true` — Force enable automatic update checks at startup for all users
-- `false` — Force disable automatic update checks at startup for all users
-- Omit the property — Allow users to control this setting themselves
+- `Update_CheckForUpdatesAtStartup`:
+ - `true` — Force enable automatic update checks at startup for all users
+ - `false` — Force disable automatic update checks at startup for all users
+ - Omit the property — Allow users to control this setting themselves
+- `Profiles_FolderLocation` / `Settings_FolderLocation`:
+ - Absolute path (e.g., `C:\\Path\\To\\Profiles`)
+ - Path with environment variables (e.g., `%UserProfile%\\NETworkManager\\Settings`)
+ - UNC path (e.g., `\\\\Server\\Shared\\NETworkManager\\Profiles`)
+ - Omit the property — Allow the default location logic to apply
**Example `config.json`:**
```json
{
- "Update_CheckForUpdatesAtStartup": false
+ "Update_CheckForUpdatesAtStartup": false,
+ "Profiles_FolderLocation": "\\\\Server\\Shared\\NETworkManager\\Profiles",
+ "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings"
}
```
diff --git a/Website/blog/2026-02-19-system-wide-policies-for-enterprise-deployments/system-wide-policy-indicator.png b/Website/blog/2026-02-22-system-wide-policies-for-enterprise-deployments/system-wide-policy-indicator.png
similarity index 100%
rename from Website/blog/2026-02-19-system-wide-policies-for-enterprise-deployments/system-wide-policy-indicator.png
rename to Website/blog/2026-02-22-system-wide-policies-for-enterprise-deployments/system-wide-policy-indicator.png
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index 0490dc5ec8..384dd85fc9 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -48,6 +48,7 @@ Release date: **xx.xx.2025**
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)
@@ -57,14 +58,19 @@ Release date: **xx.xx.2025**
:::
+**Profiles**
+
+- Profiles folder location can now be changed by the user in the settings (see [Profiles > Location](../settings/profiles.md#location) documentation for more information). [#3340](https://github.com/BornToBeRoot/NETworkManager/pull/3340)
+- Profiles folder location can now be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3340](https://github.com/BornToBeRoot/NETworkManager/pull/3340)
+
**Settings**
-- Settings folder location can now be changed by the user in the settings (see [Settings > Location](../settings/settings.md#location) documentation for more information). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3324](https://github.com/BornToBeRoot/NETworkManager/pull/3324)
+- Settings folder location can now be changed by the user in the settings (see [Settings > Location](../settings/settings.md#location) documentation for more information). [#3324](https://github.com/BornToBeRoot/NETworkManager/pull/3324)
+- Settings folder location can now be configured via a system-wide policy, allowing administrators to enforce a specific location for all users. [#3324](https://github.com/BornToBeRoot/NETworkManager/pull/3324)
**Update**
- The update check at startup can now be configured via a system-wide policy, allowing administrators to control whether the application checks for updates (see [Update > Check for updates at startup](../settings/update.md#check-for-updates-at-startup) documentation for more information). [#3313](https://github.com/BornToBeRoot/NETworkManager/pull/3313)
-
- New language Ukrainian (`uk-UA`) has been added. Thanks to [@vadickkt](https://github.com/vadickkt) [#3240](https://github.com/BornToBeRoot/NETworkManager/pull/3240)
- Migrated all dialogs to child windows for improved usability and accessibility. [#3271](https://github.com/BornToBeRoot/NETworkManager/pull/3271)
diff --git a/Website/docs/settings/profiles.md b/Website/docs/settings/profiles.md
index 186924aa4b..bee88adbda 100644
--- a/Website/docs/settings/profiles.md
+++ b/Website/docs/settings/profiles.md
@@ -17,6 +17,30 @@ Folder where the application profiles are stored.
| Setup / Archiv | `%UserProfile%\Documents\NETworkManager\Profiles` |
| Portable | `\Profiles` |
+
+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:** `Profiles_FolderLocation`
+
+**Values:**
+
+- Absolute path (e.g., `C:\\Path\\To\\Profiles`)
+- Path with environment variables (e.g., `%UserProfile%\\NETworkManager\\Profiles`)
+- UNC path (e.g., `\\\\server\\share$\\NETworkManager\\Profiles`)
+- Omit the property to allow the default location logic to apply (portable vs. non-portable)
+
+**Example:**
+
+```json
+{
+ "Profiles_FolderLocation": "%UserProfile%\\NETworkManager\\Profiles"
+}
+```
+
+
+
:::note
**Recommendation**
diff --git a/Website/docs/settings/settings.md b/Website/docs/settings/settings.md
index 4164bf37b8..8ed1f795de 100644
--- a/Website/docs/settings/settings.md
+++ b/Website/docs/settings/settings.md
@@ -22,7 +22,7 @@ Folder where the application settings are stored.
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`
+**Policy Property:** `Settings_FolderLocation`
**Values:**
@@ -35,7 +35,7 @@ This setting can be controlled by administrators using a system-wide policy. See
```json
{
- "SettingsFolderLocation": "%UserProfile%\\NETworkManager\\Settings"
+ "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings"
}
```
diff --git a/Website/docs/system-wide-policies.md b/Website/docs/system-wide-policies.md
index 1756d5755a..a53dce282d 100644
--- a/Website/docs/system-wide-policies.md
+++ b/Website/docs/system-wide-policies.md
@@ -36,8 +36,9 @@ The `config.json` file uses a simple JSON structure to define policy values. An
```json
{
+ "Profiles_FolderLocation": "\\\\Server\\Shared\\NETworkManager\\Profiles",
+ "Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings",
"Update_CheckForUpdatesAtStartup": false,
- "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
}
```