From cfa7ef642ca982f5126b21fde055cb38390eaf33 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Feb 2026 22:38:48 +0000
Subject: [PATCH 01/15] Initial plan
From 26587882bdfdb7014767be5e07449fe964e11709 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Feb 2026 22:49:41 +0000
Subject: [PATCH 02/15] Add profile file location management with policy
support and rename folder location properties
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
---
.../Resources/Strings.Designer.cs | 22 +++
.../Resources/Strings.resx | 10 +
.../NETworkManager.Profiles/ProfileManager.cs | 115 ++++++++++-
.../LocalSettingsInfo.cs | 12 +-
Source/NETworkManager.Settings/PolicyInfo.cs | 7 +-
.../NETworkManager.Settings/PolicyManager.cs | 3 +-
.../NETworkManager.Settings/SettingsInfo.cs | 19 ++
.../SettingsManager.cs | 22 +--
.../config.json.example | 3 +-
.../ViewModels/SettingsProfilesViewModel.cs | 141 ++++++++++++++
.../ViewModels/SettingsSettingsViewModel.cs | 6 +-
.../Views/SettingsProfilesView.xaml | 184 ++++++++++++++++--
.../Views/SettingsProfilesView.xaml.cs | 17 ++
Website/docs/changelog/next-release.md | 5 +
Website/docs/settings/profiles.md | 24 +++
Website/docs/settings/settings.md | 4 +-
Website/docs/system-wide-policies.md | 2 +-
17 files changed, 544 insertions(+), 52 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index 40ef350d30..b431a282ac 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -1475,6 +1475,17 @@ public static string ChangeLocationSettingsMessage {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die The location is changed and the application is restarted afterwards.
+ ///
+ ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt.
+ ///
+ public static string ChangeLocationProfilesMessage {
+ get {
+ return ResourceManager.GetString("ChangeLocationProfilesMessage", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt.
///
@@ -8902,6 +8913,17 @@ public static string RestoreDefaultLocationSettingsMessage {
}
}
+ ///
+ /// Sucht eine lokalisierte Zeichenfolge, die The default path is restored and the application is restarted afterwards.
+ ///
+ ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt.
+ ///
+ public static string RestoreDefaultLocationProfilesMessage {
+ get {
+ return ResourceManager.GetString("RestoreDefaultLocationProfilesMessage", resourceCulture);
+ }
+ }
+
///
/// Sucht eine lokalisierte Zeichenfolge, die Restore defaults ähnelt.
///
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index ad2eb07c93..550f519f38 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3988,4 +3988,14 @@ You can copy the “settings.json” file from "{0}" to "{1}" to migrate your pr
Enter a valid folder path!
+
+ The location is changed and the application is restarted afterwards.
+
+You can copy your profile files from “{0}” to “{1}” to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten.
+
+
+ The default path is restored and the application is restarted afterwards.
+
+You can copy your profile files from “{0}” to “{1}” to migrate your previous 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..6978c51d45 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -197,14 +197,121 @@ private static void ProfilesUpdated(bool profilesChanged = true)
///
/// Method to get the path of the profiles folder.
+ /// Priority: 1. Policy override, 2. Custom user path (from SettingsInfo), 3. Portable/default.
///
/// 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 = ValidateProfilesFolderPath(
+ PolicyManager.Current.Profiles_FolderLocation,
+ "Policy-provided",
+ "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_CustomProfilesFolderLocation))
+ {
+ var validatedPath = ValidateProfilesFolderPath(
+ SettingsManager.Current.Profiles_CustomProfilesFolderLocation,
+ "Custom",
+ "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);
+ }
+
+ ///
+ /// Validates a profiles 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 ValidateProfilesFolderPath(string path, string pathSource, string fallbackMessage)
+ {
+ // Expand environment variables first (e.g. %userprofile%\profiles -> C:\Users\...\profiles)
+ path = Environment.ExpandEnvironmentVariables(path);
+
+ // Validate that the path is rooted (absolute)
+ if (!Path.IsPathRooted(path))
+ {
+ Log.Error($"{pathSource} Profiles_FolderLocation 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} Profiles_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
+ return null;
+ }
+
+ return Path.TrimEndingDirectorySeparator(fullPath);
+ }
+ catch (ArgumentException ex)
+ {
+ Log.Error($"{pathSource} Profiles_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (NotSupportedException ex)
+ {
+ Log.Error($"{pathSource} Profiles_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (SecurityException ex)
+ {
+ Log.Error($"Insufficient permissions to access {pathSource} Profiles_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (PathTooLongException ex)
+ {
+ Log.Error($"{pathSource} Profiles_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
+ catch (IOException ex)
+ {
+ Log.Error($"{pathSource} Profiles_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
+ return null;
+ }
}
///
diff --git a/Source/NETworkManager.Settings/LocalSettingsInfo.cs b/Source/NETworkManager.Settings/LocalSettingsInfo.cs
index 6c7d1a692f..b3388d396e 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..55d6767084 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("Settings_FolderLocation")]
+ public string? Settings_FolderLocation { get; set; }
+
+ [JsonPropertyName("Profiles_FolderLocation")]
+ public string? Profiles_FolderLocation { get; set; }
}
diff --git a/Source/NETworkManager.Settings/PolicyManager.cs b/Source/NETworkManager.Settings/PolicyManager.cs
index ddbdfd3d38..26063ac16b 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 - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "Not set"}");
+ Log.Info($"System-wide policy - Profiles_FolderLocation: {Current.Profiles_FolderLocation ?? "Not set"}");
}
}
catch (Exception ex)
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 5e810c8ec3..0bbf9d31f1 100644
--- a/Source/NETworkManager.Settings/SettingsInfo.cs
+++ b/Source/NETworkManager.Settings/SettingsInfo.cs
@@ -622,6 +622,25 @@ public int Profiles_MaximumNumberOfBackups
}
}
+ private string _profiles_CustomProfilesFolderLocation;
+
+ ///
+ /// Custom profiles folder location set by the user.
+ /// When set, overrides the default profiles folder location.
+ ///
+ public string Profiles_CustomProfilesFolderLocation
+ {
+ get => _profiles_CustomProfilesFolderLocation;
+ set
+ {
+ if (value == _profiles_CustomProfilesFolderLocation)
+ return;
+
+ _profiles_CustomProfilesFolderLocation = value;
+ OnPropertyChanged();
+ }
+ }
+
// Settings
private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled;
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 8a289a632d..6852016be9 100644
--- a/Source/NETworkManager.Settings/SettingsManager.cs
+++ b/Source/NETworkManager.Settings/SettingsManager.cs
@@ -79,10 +79,10 @@ public static class SettingsManager
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,
+ PolicyManager.Current.Settings_FolderLocation,
"Policy-provided",
"next priority");
@@ -92,10 +92,10 @@ 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,
+ LocalSettingsManager.Current.Settings_FolderLocation,
"Custom",
"default location");
@@ -144,7 +144,7 @@ private static string ValidateSettingsFolderPath(string path, string pathSource,
// 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}.");
+ Log.Error($"{pathSource} Settings_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}.");
return null;
}
@@ -157,7 +157,7 @@ private static string ValidateSettingsFolderPath(string path, string pathSource,
// 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}.");
+ Log.Error($"{pathSource} Settings_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
return null;
}
@@ -165,27 +165,27 @@ private static string ValidateSettingsFolderPath(string path, string pathSource,
}
catch (ArgumentException ex)
{
- Log.Error($"{pathSource} SettingsFolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
+ Log.Error($"{pathSource} Settings_FolderLocation 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);
+ Log.Error($"{pathSource} Settings_FolderLocation 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);
+ Log.Error($"Insufficient permissions to access {pathSource} Settings_FolderLocation: {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);
+ Log.Error($"{pathSource} Settings_FolderLocation 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);
+ Log.Error($"{pathSource} Settings_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
return null;
}
}
diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example
index 0a47ffa700..3747efaeaa 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"
+ "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings",
+ "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles"
}
\ No newline at end of file
diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index e2c58d980d..c9efb56184 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,11 +35,55 @@ 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 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 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 readonly ICollectionView _profileFiles;
public ICollectionView ProfileFiles
@@ -126,6 +171,7 @@ public SettingsProfilesViewModel()
private void LoadSettings()
{
Location = ProfileManager.GetProfilesFolderLocation();
+ IsDefaultLocation = string.Equals(Location, ProfileManager.GetDefaultProfilesFolderLocation(), StringComparison.OrdinalIgnoreCase);
IsDailyBackupEnabled = SettingsManager.Current.Profiles_IsDailyBackupEnabled;
MaximumNumberOfBackups = SettingsManager.Current.Profiles_MaximumNumberOfBackups;
}
@@ -141,6 +187,101 @@ private static void OpenLocationAction()
Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation());
}
+ ///
+ /// 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;
+ }
+
+ ///
+ /// 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;
+ }
+
+ ///
+ /// 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.ChangeLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), Location),
+ ChildWindowIcon.Question,
+ Strings.Change);
+
+ if (!result)
+ return;
+
+ // Save settings 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).
+ SettingsManager.Save();
+
+ // Set new location in SettingsInfo
+ SettingsManager.Current.Profiles_CustomProfilesFolderLocation = Location;
+ SettingsManager.Save();
+
+ // 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.RestoreDefaultLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()),
+ ChildWindowIcon.Question,
+ Strings.Restore);
+
+ if (!result)
+ return;
+
+ // Save settings 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).
+ SettingsManager.Save();
+
+ // Clear custom location to revert to default
+ SettingsManager.Current.Profiles_CustomProfilesFolderLocation = null;
+ SettingsManager.Save();
+
+ // Restart the application
+ (Application.Current.MainWindow as MainWindow)?.RestartApplication();
+ }
+
public ICommand AddProfileFileCommand => new RelayCommand(async _ => await AddProfileFileAction().ConfigureAwait(false));
private async Task AddProfileFileAction()
diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
index e14ee3639c..b726b0cab9 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.
@@ -265,7 +265,7 @@ private async Task ChangeLocationAction()
SettingsManager.Save();
// Set new location
- LocalSettingsManager.Current.SettingsFolderLocation = Location;
+ LocalSettingsManager.Current.Settings_FolderLocation = Location;
LocalSettingsManager.Save();
// Restart the application
@@ -301,7 +301,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
diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml
index 0989c4a24d..3a19703688 100644
--- a/Source/NETworkManager/Views/SettingsProfilesView.xaml
+++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml
@@ -10,36 +10,178 @@
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">
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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)
@@ -61,6 +62,10 @@ Release date: **xx.xx.2025**
- 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)
+**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). Additionally, it can be configured via a system-wide policy, allowing administrators to enforce a specific location for all users.
+
**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)
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..103b1a8302 100644
--- a/Website/docs/system-wide-policies.md
+++ b/Website/docs/system-wide-policies.md
@@ -37,7 +37,7 @@ The `config.json` file uses a simple JSON structure to define policy values. An
```json
{
"Update_CheckForUpdatesAtStartup": false,
- "SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
+ "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
}
```
From 28cf394e9c74383bc595a382c37bbd5cf71c8b7b Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 00:59:24 +0100
Subject: [PATCH 03/15] Chore: Update strings
---
.../Resources/Strings.Designer.cs | 62 +++++++++----------
.../Resources/Strings.resx | 20 +++---
.../ViewModels/SettingsProfilesViewModel.cs | 4 +-
.../ViewModels/SettingsSettingsViewModel.cs | 4 +-
4 files changed, 45 insertions(+), 45 deletions(-)
diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
index b431a282ac..f7699adf75 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
+++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs
@@ -1465,51 +1465,51 @@ 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.
+ /// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt.
///
- public static string ChangeLocationSettingsMessage {
+ public static string Changelog {
get {
- return ResourceManager.GetString("ChangeLocationSettingsMessage", resourceCulture);
+ return ResourceManager.GetString("Changelog", resourceCulture);
}
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die The location is changed and the application is restarted afterwards.
- ///
- ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt.
+ /// Sucht eine lokalisierte Zeichenfolge, die Change master password ähnelt.
///
- public static string ChangeLocationProfilesMessage {
+ public static string ChangeMasterPassword {
get {
- return ResourceManager.GetString("ChangeLocationProfilesMessage", resourceCulture);
+ return ResourceManager.GetString("ChangeMasterPassword", resourceCulture);
}
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die Changelog ähnelt.
+ /// Sucht eine lokalisierte Zeichenfolge, die Change Master Password... ähnelt.
///
- public static string Changelog {
+ public static string ChangeMasterPasswordDots {
get {
- return ResourceManager.GetString("Changelog", resourceCulture);
+ return ResourceManager.GetString("ChangeMasterPasswordDots", resourceCulture);
}
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die Change master password ähnelt.
+ /// 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 ChangeMasterPassword {
+ public static string ChangeProfilesLocationMessage {
get {
- return ResourceManager.GetString("ChangeMasterPassword", resourceCulture);
+ return ResourceManager.GetString("ChangeProfilesLocationMessage", resourceCulture);
}
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die Change Master Password... ähnelt.
+ /// 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 ChangeMasterPasswordDots {
+ public static string ChangeSettingsLocationMessage {
get {
- return ResourceManager.GetString("ChangeMasterPasswordDots", resourceCulture);
+ return ResourceManager.GetString("ChangeSettingsLocationMessage", resourceCulture);
}
}
@@ -8903,33 +8903,33 @@ 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);
}
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die The default path is restored and the application is restarted afterwards.
- ///
- ///You can copy your profile files from "{0}" to "{1}" to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten. ähnelt.
+ /// Sucht eine lokalisierte Zeichenfolge, die Restore defaults ähnelt.
///
- public static string RestoreDefaultLocationProfilesMessage {
+ public static string RestoreDefaults {
get {
- return ResourceManager.GetString("RestoreDefaultLocationProfilesMessage", resourceCulture);
+ return ResourceManager.GetString("RestoreDefaults", resourceCulture);
}
}
///
- /// Sucht eine lokalisierte Zeichenfolge, die Restore defaults ähnelt.
+ /// 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 RestoreDefaults {
+ public static string RestoreDefaultSettingsLocationMessage {
get {
- return ResourceManager.GetString("RestoreDefaults", resourceCulture);
+ return ResourceManager.GetString("RestoreDefaultSettingsLocationMessage", resourceCulture);
}
}
diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx
index 550f519f38..7c2471b85e 100644
--- a/Source/NETworkManager.Localization/Resources/Strings.resx
+++ b/Source/NETworkManager.Localization/Resources/Strings.resx
@@ -3972,30 +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 location is changed and the application is restarted afterwards.
+
+ The profiles location is changed and the application is restarted afterwards.
-You can copy your profile files from “{0}” to “{1}” to migrate your previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten.
+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 path is restored and the application is restarted afterwards.
+
+ 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 previous profiles, if necessary. The application must be closed for this to prevent the profiles from being overwritten.
+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/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index c9efb56184..3a02eff54c 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -231,7 +231,7 @@ private async Task ChangeLocationAction()
{
var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
Strings.ChangeLocationQuestion,
- string.Format(Strings.ChangeLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), Location),
+ string.Format(Strings.ChangeProfilesLocationMessage, ProfileManager.GetProfilesFolderLocation(), Location),
ChildWindowIcon.Question,
Strings.Change);
@@ -263,7 +263,7 @@ private async Task RestoreDefaultLocationActionAsync()
{
var result = await DialogHelper.ShowConfirmationMessageAsync(Application.Current.MainWindow,
Strings.RestoreDefaultLocationQuestion,
- string.Format(Strings.RestoreDefaultLocationProfilesMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()),
+ string.Format(Strings.RestoreDefaultProfilesLocationMessage, ProfileManager.GetProfilesFolderLocation(), ProfileManager.GetDefaultProfilesFolderLocation()),
ChildWindowIcon.Question,
Strings.Restore);
diff --git a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
index b726b0cab9..ecdba124b7 100644
--- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
@@ -253,7 +253,7 @@ 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);
@@ -289,7 +289,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);
From cdbb41f890d9266625d8e175ecf8e70bc58cc8b7 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:02:49 +0100
Subject: [PATCH 04/15] Docs: ...
---
Website/docs/system-wide-policies.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Website/docs/system-wide-policies.md b/Website/docs/system-wide-policies.md
index 103b1a8302..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,
- "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
}
```
From 29ea56f2a443f3dcb3dba1771f00747a66156c86 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:04:39 +0100
Subject: [PATCH 05/15] Docs: #3340
---
Website/docs/changelog/next-release.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md
index d44e95fbba..410eb9c036 100644
--- a/Website/docs/changelog/next-release.md
+++ b/Website/docs/changelog/next-release.md
@@ -58,13 +58,13 @@ Release date: **xx.xx.2025**
:::
-**Settings**
+**Profiles**
-- 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)
+- Profiles folder location can now be changed by the user in the settings (see [Profiles > Location](../settings/profiles.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. [#3340](https://github.com/BornToBeRoot/NETworkManager/pull/3340)
-**Profiles**
+**Settings**
-- Profiles folder location can now be changed by the user in the settings (see [Profiles > Location](../settings/profiles.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.
+- 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)
**Update**
From 37ef327051586e682b6a59080f8e5b34260edf44 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:06:48 +0100
Subject: [PATCH 06/15] Update config.json.example
---
Source/NETworkManager.Settings/config.json.example | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example
index 3747efaeaa..008da6fc63 100644
--- a/Source/NETworkManager.Settings/config.json.example
+++ b/Source/NETworkManager.Settings/config.json.example
@@ -1,5 +1,5 @@
{
"Update_CheckForUpdatesAtStartup": false,
- "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings",
- "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles"
+ "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles",
+ "Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
}
\ No newline at end of file
From 484edb5a3131e7f8e6c2185235817f729866599f Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:15:55 +0100
Subject: [PATCH 07/15] Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
Source/NETworkManager/Views/SettingsProfilesView.xaml | 2 +-
Source/NETworkManager/Views/SettingsProfilesView.xaml.cs | 6 ++++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/Source/NETworkManager/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml
index 3a19703688..52b54f7010 100644
--- a/Source/NETworkManager/Views/SettingsProfilesView.xaml
+++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml
@@ -17,7 +17,7 @@
-
+
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
From 2a53696218b99584f294180badfad2c9e1d47527 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:16:07 +0100
Subject: [PATCH 08/15] Chore: Minor improvements
---
Source/NETworkManager.Settings/LocalSettingsInfo.cs | 2 +-
Source/NETworkManager.Settings/PolicyInfo.cs | 6 +++---
Source/NETworkManager.Settings/PolicyManager.cs | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Source/NETworkManager.Settings/LocalSettingsInfo.cs b/Source/NETworkManager.Settings/LocalSettingsInfo.cs
index b3388d396e..79c02a1937 100644
--- a/Source/NETworkManager.Settings/LocalSettingsInfo.cs
+++ b/Source/NETworkManager.Settings/LocalSettingsInfo.cs
@@ -34,7 +34,7 @@ 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 _settings_FolderLocation;
diff --git a/Source/NETworkManager.Settings/PolicyInfo.cs b/Source/NETworkManager.Settings/PolicyInfo.cs
index 55d6767084..811360e765 100644
--- a/Source/NETworkManager.Settings/PolicyInfo.cs
+++ b/Source/NETworkManager.Settings/PolicyInfo.cs
@@ -11,9 +11,9 @@ public class PolicyInfo
[JsonPropertyName("Update_CheckForUpdatesAtStartup")]
public bool? Update_CheckForUpdatesAtStartup { get; set; }
- [JsonPropertyName("Settings_FolderLocation")]
- public string? Settings_FolderLocation { 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 26063ac16b..e7708db5fa 100644
--- a/Source/NETworkManager.Settings/PolicyManager.cs
+++ b/Source/NETworkManager.Settings/PolicyManager.cs
@@ -84,8 +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 - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "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)
From 620315e32fc83c4adad764ecf6569a5192b6f310 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:17:29 +0100
Subject: [PATCH 09/15] Update SettingsSettingsView.xaml.cs
---
Source/NETworkManager/Views/SettingsSettingsView.xaml.cs | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs b/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs
index 8c840314d5..54c6a604ff 100644
--- a/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs
+++ b/Source/NETworkManager/Views/SettingsSettingsView.xaml.cs
@@ -31,13 +31,15 @@ private void TextBoxLocation_Drop(object sender, DragEventArgs e)
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
- if (files != null)
+ if (files is { Length: > 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
+}
From 37209464346550f95c9f6142e63c77d30e6765f4 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 01:59:01 +0100
Subject: [PATCH 10/15] Chore: Minor refactoring
---
.../NETworkManager.Profiles/ProfileManager.cs | 76 +++----------------
.../NETworkManager.Settings/SettingsInfo.cs | 36 ++++-----
.../SettingsManager.cs | 72 ++----------------
.../DirectoryHelper.cs | 70 +++++++++++++++++
.../ViewModels/SettingsProfilesViewModel.cs | 4 +-
5 files changed, 106 insertions(+), 152 deletions(-)
diff --git a/Source/NETworkManager.Profiles/ProfileManager.cs b/Source/NETworkManager.Profiles/ProfileManager.cs
index 6978c51d45..c32ae88977 100644
--- a/Source/NETworkManager.Profiles/ProfileManager.cs
+++ b/Source/NETworkManager.Profiles/ProfileManager.cs
@@ -197,7 +197,10 @@ private static void ProfilesUpdated(bool profilesChanged = true)
///
/// Method to get the path of the profiles folder.
- /// Priority: 1. Policy override, 2. Custom user path (from SettingsInfo), 3. Portable/default.
+ /// 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()
@@ -205,9 +208,10 @@ public static string GetProfilesFolderLocation()
// 1. Policy override takes precedence (for IT administrators)
if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation))
{
- var validatedPath = ValidateProfilesFolderPath(
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
PolicyManager.Current.Profiles_FolderLocation,
"Policy-provided",
+ nameof(PolicyInfo.Profiles_FolderLocation),
"next priority");
if (validatedPath != null)
@@ -216,11 +220,12 @@ public static string GetProfilesFolderLocation()
// 2. Custom user-configured path (not available in portable mode)
if (!ConfigurationManager.Current.IsPortable &&
- !string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_CustomProfilesFolderLocation))
+ !string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_FolderLocation))
{
- var validatedPath = ValidateProfilesFolderPath(
- SettingsManager.Current.Profiles_CustomProfilesFolderLocation,
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
+ SettingsManager.Current.Profiles_FolderLocation,
"Custom",
+ nameof(SettingsInfo.Profiles_FolderLocation),
"default location");
if (validatedPath != null)
@@ -253,67 +258,6 @@ public static string GetPortableProfilesFolderLocation()
return Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName);
}
- ///
- /// Validates a profiles 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 ValidateProfilesFolderPath(string path, string pathSource, string fallbackMessage)
- {
- // Expand environment variables first (e.g. %userprofile%\profiles -> C:\Users\...\profiles)
- path = Environment.ExpandEnvironmentVariables(path);
-
- // Validate that the path is rooted (absolute)
- if (!Path.IsPathRooted(path))
- {
- Log.Error($"{pathSource} Profiles_FolderLocation 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} Profiles_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
- return null;
- }
-
- return Path.TrimEndingDirectorySeparator(fullPath);
- }
- catch (ArgumentException ex)
- {
- Log.Error($"{pathSource} Profiles_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (NotSupportedException ex)
- {
- Log.Error($"{pathSource} Profiles_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (SecurityException ex)
- {
- Log.Error($"Insufficient permissions to access {pathSource} Profiles_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (PathTooLongException ex)
- {
- Log.Error($"{pathSource} Profiles_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (IOException ex)
- {
- Log.Error($"{pathSource} Profiles_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- }
-
///
/// Method to get the path of the profiles backup folder.
///
diff --git a/Source/NETworkManager.Settings/SettingsInfo.cs b/Source/NETworkManager.Settings/SettingsInfo.cs
index 0bbf9d31f1..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
@@ -622,25 +637,6 @@ public int Profiles_MaximumNumberOfBackups
}
}
- private string _profiles_CustomProfilesFolderLocation;
-
- ///
- /// Custom profiles folder location set by the user.
- /// When set, overrides the default profiles folder location.
- ///
- public string Profiles_CustomProfilesFolderLocation
- {
- get => _profiles_CustomProfilesFolderLocation;
- set
- {
- if (value == _profiles_CustomProfilesFolderLocation)
- return;
-
- _profiles_CustomProfilesFolderLocation = value;
- OnPropertyChanged();
- }
- }
-
// Settings
private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled;
diff --git a/Source/NETworkManager.Settings/SettingsManager.cs b/Source/NETworkManager.Settings/SettingsManager.cs
index 6852016be9..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,6 +73,10 @@ 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()
@@ -81,9 +84,10 @@ public static string GetSettingsFolderLocation()
// 1. Policy override takes precedence (for IT administrators)
if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation))
{
- var validatedPath = ValidateSettingsFolderPath(
+ var validatedPath = DirectoryHelper.ValidateFolderPath(
PolicyManager.Current.Settings_FolderLocation,
"Policy-provided",
+ nameof(PolicyInfo.Settings_FolderLocation),
"next priority");
if (validatedPath != null)
@@ -94,9 +98,10 @@ public static string GetSettingsFolderLocation()
if (!ConfigurationManager.Current.IsPortable &&
!string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.Settings_FolderLocation))
{
- var validatedPath = ValidateSettingsFolderPath(
+ 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} Settings_FolderLocation 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} Settings_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
- return null;
- }
-
- return Path.TrimEndingDirectorySeparator(fullPath);
- }
- catch (ArgumentException ex)
- {
- Log.Error($"{pathSource} Settings_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (NotSupportedException ex)
- {
- Log.Error($"{pathSource} Settings_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (SecurityException ex)
- {
- Log.Error($"Insufficient permissions to access {pathSource} Settings_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (PathTooLongException ex)
- {
- Log.Error($"{pathSource} Settings_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
- return null;
- }
- catch (IOException ex)
- {
- Log.Error($"{pathSource} Settings_FolderLocation 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.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 3a02eff54c..fa9c3b9476 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -243,7 +243,7 @@ private async Task ChangeLocationAction()
SettingsManager.Save();
// Set new location in SettingsInfo
- SettingsManager.Current.Profiles_CustomProfilesFolderLocation = Location;
+ SettingsManager.Current.Profiles_FolderLocation = Location;
SettingsManager.Save();
// Restart the application
@@ -275,7 +275,7 @@ private async Task RestoreDefaultLocationActionAsync()
SettingsManager.Save();
// Clear custom location to revert to default
- SettingsManager.Current.Profiles_CustomProfilesFolderLocation = null;
+ SettingsManager.Current.Profiles_FolderLocation = null;
SettingsManager.Save();
// Restart the application
From 09af5ced39c210bc4c0d3c584fb682d24de3036d Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 02:25:35 +0100
Subject: [PATCH 11/15] Chore: Updates
---
.../ViewModels/SettingsProfilesViewModel.cs | 83 ++++++++++++-----
.../ViewModels/SettingsSettingsViewModel.cs | 91 ++++++++-----------
2 files changed, 94 insertions(+), 80 deletions(-)
diff --git a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
index fa9c3b9476..a2e660327e 100644
--- a/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs
@@ -48,6 +48,9 @@ public string Location
///
public bool IsLocationManagedByPolicy => !string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation);
+ ///
+ /// Private field of property.
+ ///
private bool _isLocationChanged;
///
@@ -66,6 +69,9 @@ public bool IsLocationChanged
}
}
+ ///
+ /// Private field of property.
+ ///
private bool _isDefaultLocation;
///
@@ -84,8 +90,14 @@ public bool IsDefaultLocation
}
}
+ ///
+ /// Private field of property.
+ ///
private readonly ICollectionView _profileFiles;
+ ///
+ /// Gets the collection view of profile files.
+ ///
public ICollectionView ProfileFiles
{
get => _profileFiles;
@@ -99,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;
@@ -114,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;
@@ -132,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;
@@ -153,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;
@@ -168,6 +203,9 @@ public SettingsProfilesViewModel()
_isLoading = false;
}
+ ///
+ /// Load view specific settings.
+ ///
private void LoadSettings()
{
Location = ProfileManager.GetProfilesFolderLocation();
@@ -179,14 +217,6 @@ private void LoadSettings()
#endregion
#region ICommands & Actions
-
- public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction());
-
- private static void OpenLocationAction()
- {
- Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation());
- }
-
///
/// Gets the command that opens the location folder selection dialog.
///
@@ -209,14 +239,16 @@ private void BrowseLocationFolderAction()
Location = dialog.SelectedPath;
}
+
///
- /// Sets the location path based on the provided drag-and-drop input.
- ///
- /// The path to set as the location.
- public void SetLocationPathFromDragDrop(string path)
+ /// Gets the command that initiates the action to change the location.
+ ///
+ public ICommand OpenLocationCommand => new RelayCommand(_ => OpenLocationAction());
+
+ private static void OpenLocationAction()
{
- Location = path;
- }
+ Process.Start("explorer.exe", ProfileManager.GetProfilesFolderLocation());
+ }
///
/// Gets the command that initiates the action to change the location.
@@ -238,13 +270,8 @@ private async Task ChangeLocationAction()
if (!result)
return;
- // Save settings 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).
- SettingsManager.Save();
-
// Set new location in SettingsInfo
SettingsManager.Current.Profiles_FolderLocation = Location;
- SettingsManager.Save();
// Restart the application
(Application.Current.MainWindow as MainWindow)?.RestartApplication();
@@ -270,14 +297,9 @@ private async Task RestoreDefaultLocationActionAsync()
if (!result)
return;
- // Save settings 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).
- SettingsManager.Save();
-
// Clear custom location to revert to default
SettingsManager.Current.Profiles_FolderLocation = null;
- SettingsManager.Save();
-
+
// Restart the application
(Application.Current.MainWindow as MainWindow)?.RestartApplication();
}
@@ -551,4 +573,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 ecdba124b7..6212c228ba 100644
--- a/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
+++ b/Source/NETworkManager/ViewModels/SettingsSettingsViewModel.cs
@@ -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,12 +213,8 @@ 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 profiles 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()
{
@@ -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
}
From 09c11864d77fe9bf31fbe44665c2c09d66f5f52e Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 22 Feb 2026 02:50:14 +0100
Subject: [PATCH 12/15] Feature: Improve view
---
.../config.json.example | 4 +-
.../Views/SettingsProfilesView.xaml | 58 +++++++++---------
.../Views/SettingsSettingsView.xaml | 60 ++++++++++---------
3 files changed, 66 insertions(+), 56 deletions(-)
diff --git a/Source/NETworkManager.Settings/config.json.example b/Source/NETworkManager.Settings/config.json.example
index 008da6fc63..2bf8307cfb 100644
--- a/Source/NETworkManager.Settings/config.json.example
+++ b/Source/NETworkManager.Settings/config.json.example
@@ -1,5 +1,5 @@
{
"Update_CheckForUpdatesAtStartup": false,
- "Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles",
- "Settings_FolderLocation": "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/Views/SettingsProfilesView.xaml b/Source/NETworkManager/Views/SettingsProfilesView.xaml
index 52b54f7010..efa4d9c5d4 100644
--- a/Source/NETworkManager/Views/SettingsProfilesView.xaml
+++ b/Source/NETworkManager/Views/SettingsProfilesView.xaml
@@ -69,20 +69,12 @@
-
-