Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions Source/NETworkManager.Localization/Resources/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions Source/NETworkManager.Localization/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3972,20 +3972,30 @@ If you click Cancel, the profile file will remain unencrypted.</value>
<data name="RestoreDefaultLocationQuestion" xml:space="preserve">
<value>Restore default location?</value>
</data>
<data name="RestoreDefaultLocationSettingsMessage" xml:space="preserve">
<value>The default path is restored and the application is restarted afterwards.
<data name="RestoreDefaultSettingsLocationMessage" xml:space="preserve">
<value>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.</value>
</data>
<data name="ChangeLocationQuestion" xml:space="preserve">
<value>Change location?</value>
</data>
<data name="ChangeLocationSettingsMessage" xml:space="preserve">
<value>The location is changed and the application is restarted afterwards.
<data name="ChangeSettingsLocationMessage" xml:space="preserve">
<value>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.</value>
</data>
<data name="EnterValidFolderPath" xml:space="preserve">
<value>Enter a valid folder path!</value>
</data>
<data name="ChangeProfilesLocationMessage" xml:space="preserve">
<value>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.</value>
</data>
<data name="RestoreDefaultProfilesLocationMessage" xml:space="preserve">
<value>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.</value>
</data>
</root>
59 changes: 55 additions & 4 deletions Source/NETworkManager.Profiles/ProfileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,65 @@ private static void ProfilesUpdated(bool profilesChanged = true)

/// <summary>
/// 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)
/// </summary>
/// <returns>Path to the profiles folder.</returns>
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();
}

/// <summary>
/// Method to get the default profiles folder location in the user's Documents directory.
/// </summary>
/// <returns>Path to the default profiles folder location.</returns>
public static string GetDefaultProfilesFolderLocation()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
AssemblyManager.Current.Name, ProfilesFolderName);
}

/// <summary>
/// Method to get the portable profiles folder location (in the same directory as the application).
/// </summary>
/// <returns>Path to the portable profiles folder location.</returns>
public static string GetPortableProfilesFolderLocation()
{
return Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName);
}

/// <summary>
Expand Down
12 changes: 6 additions & 6 deletions Source/NETworkManager.Settings/LocalSettingsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
[JsonIgnore] public bool SettingsChanged { get; set; }

/// <summary>
/// Private field for the <see cref="SettingsFolderLocation" /> property."
/// Private field for the <see cref="Settings_FolderLocation" /> property.
/// </summary>
private string _settingsFolderLocation;
private string _settings_FolderLocation;

/// <summary>
/// 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.
/// </summary>
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();
}
}
Expand Down
7 changes: 5 additions & 2 deletions Source/NETworkManager.Settings/PolicyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
3 changes: 2 additions & 1 deletion Source/NETworkManager.Settings/PolicyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 16 additions & 1 deletion Source/NETworkManager.Settings/SettingsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 12 additions & 68 deletions Source/NETworkManager.Settings/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -74,16 +73,21 @@ public static class SettingsManager

/// <summary>
/// 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)
/// </summary>
/// <returns>Path to the settings folder.</returns>
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)
Expand All @@ -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)
Expand Down Expand Up @@ -129,67 +134,6 @@ public static string GetPortableSettingsFolderLocation()
return Path.Combine(AssemblyManager.Current.Location, SettingsFolderName);
}

/// <summary>
/// Validates a settings folder path for correctness and accessibility.
/// </summary>
/// <param name="path">The path to validate.</param>
/// <param name="pathSource">Description of the path source for logging (e.g., "Policy-provided", "Custom").</param>
/// <param name="fallbackMessage">Message describing what happens on validation failure (e.g., "next priority", "default location").</param>
/// <returns>The validated full path if valid; otherwise, null.</returns>
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;
}
}

/// <summary>
/// Method to get the path of the settings backup folder.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion Source/NETworkManager.Settings/config.json.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"Update_CheckForUpdatesAtStartup": false,
"SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
"Profiles_FolderLocation": "\\\\Server\\Shared\\NETworkManager\\Profiles",
"Settings_FolderLocation": "%UserProfile%\\NETworkManager\\Settings"
}
Loading