@@ -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>
0 commit comments