Skip to content

Commit 2658788

Browse files
CopilotBornToBeRoot
andcommitted
Add profile file location management with policy support and rename folder location properties
Co-authored-by: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
1 parent cfa7ef6 commit 2658788

File tree

17 files changed

+544
-52
lines changed

17 files changed

+544
-52
lines changed

Source/NETworkManager.Localization/Resources/Strings.Designer.cs

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Source/NETworkManager.Localization/Resources/Strings.resx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3988,4 +3988,14 @@ You can copy the “settings.json” file from "{0}" to "{1}" to migrate your pr
39883988
<data name="EnterValidFolderPath" xml:space="preserve">
39893989
<value>Enter a valid folder path!</value>
39903990
</data>
3991+
<data name="ChangeLocationProfilesMessage" xml:space="preserve">
3992+
<value>The location is changed and the application is restarted afterwards.
3993+
3994+
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.</value>
3995+
</data>
3996+
<data name="RestoreDefaultLocationProfilesMessage" xml:space="preserve">
3997+
<value>The default path is restored and the application is restarted afterwards.
3998+
3999+
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.</value>
4000+
</data>
39914001
</root>

Source/NETworkManager.Profiles/ProfileManager.cs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,121 @@ private static void ProfilesUpdated(bool profilesChanged = true)
197197

198198
/// <summary>
199199
/// Method to get the path of the profiles folder.
200+
/// Priority: 1. Policy override, 2. Custom user path (from SettingsInfo), 3. Portable/default.
200201
/// </summary>
201202
/// <returns>Path to the profiles folder.</returns>
202203
public static string GetProfilesFolderLocation()
203204
{
204-
return ConfigurationManager.Current.IsPortable
205-
? Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName)
206-
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
207-
AssemblyManager.Current.Name, ProfilesFolderName);
205+
// 1. Policy override takes precedence (for IT administrators)
206+
if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Profiles_FolderLocation))
207+
{
208+
var validatedPath = ValidateProfilesFolderPath(
209+
PolicyManager.Current.Profiles_FolderLocation,
210+
"Policy-provided",
211+
"next priority");
212+
213+
if (validatedPath != null)
214+
return validatedPath;
215+
}
216+
217+
// 2. Custom user-configured path (not available in portable mode)
218+
if (!ConfigurationManager.Current.IsPortable &&
219+
!string.IsNullOrWhiteSpace(SettingsManager.Current?.Profiles_CustomProfilesFolderLocation))
220+
{
221+
var validatedPath = ValidateProfilesFolderPath(
222+
SettingsManager.Current.Profiles_CustomProfilesFolderLocation,
223+
"Custom",
224+
"default location");
225+
226+
if (validatedPath != null)
227+
return validatedPath;
228+
}
229+
230+
// 3. Fall back to portable or default location
231+
if (ConfigurationManager.Current.IsPortable)
232+
return GetPortableProfilesFolderLocation();
233+
else
234+
return GetDefaultProfilesFolderLocation();
235+
}
236+
237+
/// <summary>
238+
/// Method to get the default profiles folder location in the user's Documents directory.
239+
/// </summary>
240+
/// <returns>Path to the default profiles folder location.</returns>
241+
public static string GetDefaultProfilesFolderLocation()
242+
{
243+
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
244+
AssemblyManager.Current.Name, ProfilesFolderName);
245+
}
246+
247+
/// <summary>
248+
/// Method to get the portable profiles folder location (in the same directory as the application).
249+
/// </summary>
250+
/// <returns>Path to the portable profiles folder location.</returns>
251+
public static string GetPortableProfilesFolderLocation()
252+
{
253+
return Path.Combine(AssemblyManager.Current.Location, ProfilesFolderName);
254+
}
255+
256+
/// <summary>
257+
/// Validates a profiles folder path for correctness and accessibility.
258+
/// </summary>
259+
/// <param name="path">The path to validate.</param>
260+
/// <param name="pathSource">Description of the path source for logging (e.g., "Policy-provided", "Custom").</param>
261+
/// <param name="fallbackMessage">Message describing what happens on validation failure (e.g., "next priority", "default location").</param>
262+
/// <returns>The validated full path if valid; otherwise, null.</returns>
263+
private static string ValidateProfilesFolderPath(string path, string pathSource, string fallbackMessage)
264+
{
265+
// Expand environment variables first (e.g. %userprofile%\profiles -> C:\Users\...\profiles)
266+
path = Environment.ExpandEnvironmentVariables(path);
267+
268+
// Validate that the path is rooted (absolute)
269+
if (!Path.IsPathRooted(path))
270+
{
271+
Log.Error($"{pathSource} Profiles_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}.");
272+
return null;
273+
}
274+
275+
// Validate that the path doesn't contain invalid characters
276+
try
277+
{
278+
// This will throw ArgumentException, NotSupportedException, SecurityException, PathTooLongException, or IOException if the path is invalid
279+
var fullPath = Path.GetFullPath(path);
280+
281+
// Check if the path is a directory (not a file)
282+
if (File.Exists(fullPath))
283+
{
284+
Log.Error($"{pathSource} Profiles_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
285+
return null;
286+
}
287+
288+
return Path.TrimEndingDirectorySeparator(fullPath);
289+
}
290+
catch (ArgumentException ex)
291+
{
292+
Log.Error($"{pathSource} Profiles_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
293+
return null;
294+
}
295+
catch (NotSupportedException ex)
296+
{
297+
Log.Error($"{pathSource} Profiles_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
298+
return null;
299+
}
300+
catch (SecurityException ex)
301+
{
302+
Log.Error($"Insufficient permissions to access {pathSource} Profiles_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
303+
return null;
304+
}
305+
catch (PathTooLongException ex)
306+
{
307+
Log.Error($"{pathSource} Profiles_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
308+
return null;
309+
}
310+
catch (IOException ex)
311+
{
312+
Log.Error($"{pathSource} Profiles_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
313+
return null;
314+
}
208315
}
209316

210317
/// <summary>

Source/NETworkManager.Settings/LocalSettingsInfo.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,23 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null)
3434
[JsonIgnore] public bool SettingsChanged { get; set; }
3535

3636
/// <summary>
37-
/// Private field for the <see cref="SettingsFolderLocation" /> property."
37+
/// Private field for the <see cref="Settings_FolderLocation" /> property."
3838
/// </summary>
39-
private string _settingsFolderLocation;
39+
private string _settings_FolderLocation;
4040

4141
/// <summary>
4242
/// Location of the folder where the local settings file is stored.
4343
/// This can be changed by the user to move the settings file to a different location.
4444
/// </summary>
45-
public string SettingsFolderLocation
45+
public string Settings_FolderLocation
4646
{
47-
get => _settingsFolderLocation;
47+
get => _settings_FolderLocation;
4848
set
4949
{
50-
if (_settingsFolderLocation == value)
50+
if (_settings_FolderLocation == value)
5151
return;
5252

53-
_settingsFolderLocation = value;
53+
_settings_FolderLocation = value;
5454
OnPropertyChanged();
5555
}
5656
}

Source/NETworkManager.Settings/PolicyInfo.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ public class PolicyInfo
1111
[JsonPropertyName("Update_CheckForUpdatesAtStartup")]
1212
public bool? Update_CheckForUpdatesAtStartup { get; set; }
1313

14-
[JsonPropertyName("SettingsFolderLocation")]
15-
public string? SettingsFolderLocation { get; set; }
14+
[JsonPropertyName("Settings_FolderLocation")]
15+
public string? Settings_FolderLocation { get; set; }
16+
17+
[JsonPropertyName("Profiles_FolderLocation")]
18+
public string? Profiles_FolderLocation { get; set; }
1619
}

Source/NETworkManager.Settings/PolicyManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ public static void Load()
8484

8585
// Log enabled settings
8686
Log.Info($"System-wide policy - Update_CheckForUpdatesAtStartup: {Current.Update_CheckForUpdatesAtStartup?.ToString() ?? "Not set"}");
87-
Log.Info($"System-wide policy - SettingsFolderLocation: {Current.SettingsFolderLocation ?? "Not set"}");
87+
Log.Info($"System-wide policy - Settings_FolderLocation: {Current.Settings_FolderLocation ?? "Not set"}");
88+
Log.Info($"System-wide policy - Profiles_FolderLocation: {Current.Profiles_FolderLocation ?? "Not set"}");
8889
}
8990
}
9091
catch (Exception ex)

Source/NETworkManager.Settings/SettingsInfo.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,25 @@ public int Profiles_MaximumNumberOfBackups
622622
}
623623
}
624624

625+
private string _profiles_CustomProfilesFolderLocation;
626+
627+
/// <summary>
628+
/// Custom profiles folder location set by the user.
629+
/// When set, overrides the default profiles folder location.
630+
/// </summary>
631+
public string Profiles_CustomProfilesFolderLocation
632+
{
633+
get => _profiles_CustomProfilesFolderLocation;
634+
set
635+
{
636+
if (value == _profiles_CustomProfilesFolderLocation)
637+
return;
638+
639+
_profiles_CustomProfilesFolderLocation = value;
640+
OnPropertyChanged();
641+
}
642+
}
643+
625644
// Settings
626645
private bool _settings_IsDailyBackupEnabled = GlobalStaticConfiguration.Settings_IsDailyBackupEnabled;
627646

Source/NETworkManager.Settings/SettingsManager.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public static class SettingsManager
7979
public static string GetSettingsFolderLocation()
8080
{
8181
// 1. Policy override takes precedence (for IT administrators)
82-
if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.SettingsFolderLocation))
82+
if (!string.IsNullOrWhiteSpace(PolicyManager.Current?.Settings_FolderLocation))
8383
{
8484
var validatedPath = ValidateSettingsFolderPath(
85-
PolicyManager.Current.SettingsFolderLocation,
85+
PolicyManager.Current.Settings_FolderLocation,
8686
"Policy-provided",
8787
"next priority");
8888

@@ -92,10 +92,10 @@ public static string GetSettingsFolderLocation()
9292

9393
// 2. Custom user-configured path (not available in portable mode)
9494
if (!ConfigurationManager.Current.IsPortable &&
95-
!string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.SettingsFolderLocation))
95+
!string.IsNullOrWhiteSpace(LocalSettingsManager.Current?.Settings_FolderLocation))
9696
{
9797
var validatedPath = ValidateSettingsFolderPath(
98-
LocalSettingsManager.Current.SettingsFolderLocation,
98+
LocalSettingsManager.Current.Settings_FolderLocation,
9999
"Custom",
100100
"default location");
101101

@@ -144,7 +144,7 @@ private static string ValidateSettingsFolderPath(string path, string pathSource,
144144
// Validate that the path is rooted (absolute)
145145
if (!Path.IsPathRooted(path))
146146
{
147-
Log.Error($"{pathSource} SettingsFolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}.");
147+
Log.Error($"{pathSource} Settings_FolderLocation is not an absolute path: {path}. Falling back to {fallbackMessage}.");
148148
return null;
149149
}
150150

@@ -157,35 +157,35 @@ private static string ValidateSettingsFolderPath(string path, string pathSource,
157157
// Check if the path is a directory (not a file)
158158
if (File.Exists(fullPath))
159159
{
160-
Log.Error($"{pathSource} SettingsFolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
160+
Log.Error($"{pathSource} Settings_FolderLocation is a file, not a directory: {path}. Falling back to {fallbackMessage}.");
161161
return null;
162162
}
163163

164164
return Path.TrimEndingDirectorySeparator(fullPath);
165165
}
166166
catch (ArgumentException ex)
167167
{
168-
Log.Error($"{pathSource} SettingsFolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
168+
Log.Error($"{pathSource} Settings_FolderLocation contains invalid characters: {path}. Falling back to {fallbackMessage}.", ex);
169169
return null;
170170
}
171171
catch (NotSupportedException ex)
172172
{
173-
Log.Error($"{pathSource} SettingsFolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
173+
Log.Error($"{pathSource} Settings_FolderLocation format is not supported: {path}. Falling back to {fallbackMessage}.", ex);
174174
return null;
175175
}
176176
catch (SecurityException ex)
177177
{
178-
Log.Error($"Insufficient permissions to access {pathSource} SettingsFolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
178+
Log.Error($"Insufficient permissions to access {pathSource} Settings_FolderLocation: {path}. Falling back to {fallbackMessage}.", ex);
179179
return null;
180180
}
181181
catch (PathTooLongException ex)
182182
{
183-
Log.Error($"{pathSource} SettingsFolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
183+
Log.Error($"{pathSource} Settings_FolderLocation path is too long: {path}. Falling back to {fallbackMessage}.", ex);
184184
return null;
185185
}
186186
catch (IOException ex)
187187
{
188-
Log.Error($"{pathSource} SettingsFolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
188+
Log.Error($"{pathSource} Settings_FolderLocation caused an I/O error: {path}. Falling back to {fallbackMessage}.", ex);
189189
return null;
190190
}
191191
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"Update_CheckForUpdatesAtStartup": false,
3-
"SettingsFolderLocation": "C:\\CustomPath\\NETworkManager\\Settings"
3+
"Settings_FolderLocation": "C:\\CustomPath\\NETworkManager\\Settings",
4+
"Profiles_FolderLocation": "C:\\CustomPath\\NETworkManager\\Profiles"
45
}

0 commit comments

Comments
 (0)